From 311bf475dbbed887f76a8dd8b26a66aa571dee4f Mon Sep 17 00:00:00 2001 From: Forest Belton Date: Thu, 30 Sep 2021 17:16:30 -0400 Subject: [PATCH] Add title song --- .vscode/settings.json | 3 +- assets/player.png | Bin 191 -> 228 bytes assets/title.uge | Bin 0 -> 68954 bytes src/actor.c | 4 +- src/actor.h | 3 +- src/audio.c | 14 + src/collision.c | 20 +- src/game.h | 4 + src/hardware.inc | 913 +++++++++++++++++++++ src/helpers.asm | 26 + src/huge.inc | 81 ++ src/huge_note_table.inc | 78 ++ src/hugedriver.asm | 1729 +++++++++++++++++++++++++++++++++++++++ src/hugedriver.h | 119 +++ src/main.c | 7 + src/map.c | 4 +- src/player.c | 26 +- src/player.h | 3 +- src/songs/title.asm | 634 ++++++++++++++ src/title.c | 5 + 20 files changed, 3656 insertions(+), 17 deletions(-) create mode 100644 assets/title.uge create mode 100644 src/audio.c create mode 100644 src/hardware.inc create mode 100644 src/huge.inc create mode 100644 src/huge_note_table.inc create mode 100644 src/hugedriver.asm create mode 100644 src/hugedriver.h create mode 100644 src/songs/title.asm diff --git a/.vscode/settings.json b/.vscode/settings.json index 56f7515..776d608 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "hardware.h": "c", "util.h": "c", "video.h": "c", - "vram.h": "c" + "vram.h": "c", + "game.h": "c" } } diff --git a/assets/player.png b/assets/player.png index fc16c4f64c3a0801e3ee7ba5a26bb85ca4bf6a76..876e27406eb8b1c7a0638064e7d08e899d143a7b 100644 GIT binary patch delta 161 zcmV;S0AByU0ptOY7YZ;40{{R3FM$eWks&yLc1c7*R49?AP5A(`Tw7*U;#;c z?WJpC+=(Kr$P^|Cp(dJ7GJ)92D|7kw4sy5CFlB{dgdhQQcZa`0q8A*X*}tFaFo$~t z2q)?KSwJ7>;gu4g9C>WN1EF|4kGm>k-C22*U)K1w)f#E$QGvAatGfWJOakZ_JQh9- P00000NkvXXu0mjfsWd}r delta 123 zcmV->0EGYK0lxu|7YZN<0{{R3ELRBrks&x{QAtEWR49?6lfTNM{kn?3LnX+^cK#lhVTnRhHYBJHy!H^(X+4w!}C|QerD^gg5YjW dezu@x9}^N00sn5e^!NY(002ovPDHLkV1f)(F%tj) diff --git a/assets/title.uge b/assets/title.uge new file mode 100644 index 0000000000000000000000000000000000000000..2f0518aa5074ca1c7d46a2dc606c334ce66a6558 GIT binary patch literal 68954 zcmeI5*>c-96o$EjJCp8JH+M<*x`|si-P?;kKxdq*-uc4QdYZ9YkJC&Zw{KD$N{11J zLrMWvE#@zeLJ$Wazz_d_q)=Pv;06L9kpA-M`pLn)qr>And2PJRW)5vpTawqN)O210HBuEzY)M|5Qqy@2)aI!z$!nr*$5v@R^H`#_ zu>lg#7t};7ODaKa2_P{HY9f{;m7ul+kQfFv5zCUwXQD<=>29C={pj@S?BMa~KWAgV zU^LH`pYG1rP0JLi=BbVRg3&x7Vk46?sEz!B(IPc(zM?4`)JA^6r~#u3#KxbmXv!2R zPEH?RUtV7SQD?R$H2MOu2}BAtwFg({rx(9JoIW0i%|=;CY(nwODUzI=Ux1j2huB1B zwXUfn56GTR#Q2yiiH#BgP^4-c3VidBh)q0S(Ufbg{d#$RaSg3CP-X>UlPp%&`xH?p z*H`+{;7n{V;!0wZisw%e+++g8CKE%TCSs{8iA^qsK~2QcP%FeRsEJq_YNZ&K|92`j zz1fvdudnr<=f4N^2Wm4&D^i=b1~pLYHCKNSTI=T{M?p>I(q9W|{e0vosL5RVYeB7_ zj~oRxnM;2ysP*%aqo5{p>8}N~em-&()MPIGwV>9|M~*%dH9ADqbK@bpnsA)Ai|x36 zam(#=z3Y0O?*~B`Mv+o+oFr+QWqDo{Wm#2ak!NWVt0)Y7{r+Ot^Mf!_agt_vp~EWG z?J^a5uIoo}Rup-n0^KZ72?b)O>i?_CDzDNiuA(ZaJl)vS+eq)Hx?Ue&k6+|@mZfQu z#Ic@UniZAn1z}w(%Q$e|$SsRd1u9I_A}>?lRVpb8eO<&4l%q>3iIdWIvpn-N&&hPr z3G~)aourIIuZ+An&Q###MOH+CtNg@`{YVwccavO&r4yu@MW_;=u95mtkr$B@Cc0(p zCrPHf&`a|$N()cdgkDnSsnSz&oHEb-G;rd$r24c7^r^T>nfPU)$AXR)K|7!5xV{;_u+`yXVTe+BW9o0peydsyG`V|al{;_>g>Z2v(Iat zeK=zFS@9HF&3F~`aE>EJ%yD=X%sw13`%H9WnHxvUKCS7jxWlVp&W$7HIJ^pGAC8!P zcoob(95MT>cnYm%#Eg!_+x8l@nr>z1_H1q@2ac?V!=_Gd(s@3wbVm40cagy^&&_5{ z={0vbkFZP3RiGTlnaPZDWP3X08SLWRY}TlIB{;G@);8xu>mqB;=F!t;2HWpLkDlwD zMJu23&i^0`K6LFa-oZFEj=nWzRN%H0Rm&LN+~_^mHW|a_@nNFJl5-YE)?)#FR$Vh{ zao~tK4nM0l`*6hU!_TVCJ{&Row5GG-4%SCsyXn*pM@-fnG1sSCy>t{=qZ~2Ep?Bba z(%J9R&d6bBq`Oad#O7$-vtCh}D>mbCOm-_CU~n^@fkwXY0JC1A>pfccth<4H(5b5B zeYUqg^m&Z#`5^*2hH)W^1yRs7Za@J8IE4t<>3}|~1rLFNt$v`L=_mKV0Um;fh=83A z=rtvH2n=lX1MN&d)e8>r5IjT#>~ug^1mPhtu+~S1_>6!O)K5JO>Yve2_=Lz*+~- zgPk=yHpdT=>*1-hqtl1%NBlnPa=v$ni1u!5jKnHdV zUI#mp5A*{D);f3t?5x=#ZzK1Rc;1AE;GvoT{6H9N^5R>T{cOfUAGe!b-po`??D6KtJ$MO<-RYy#3tsE<6Me!9z0wc6sDI zcnBVXhX#4*4m<=8!9#;Q^gcWU55Yr&JVd`dw*CK=VFp+QlJS1(;KRM0`F`@otoPL^ z-7EJIJVXg_PcvZmx+1c6K)o6_57@DMF4@(37M>);UVtl4cU&taV1&T*f^L-0^dV7GO;%X#E3JOmHHLo)() zdE_2E1P{SOgFJKu55Yt5&>#Nframe_idx = 0; PLAYER_ACTOR->frame_counter = 0; PLAYER_ACTOR->x = PLAYER.x; - PLAYER_ACTOR->y = PLAYER.y; + PLAYER_ACTOR->y = PLAYER.y_hi; } void actor_reset(void) { diff --git a/src/actor.h b/src/actor.h index 905981d..163b53d 100644 --- a/src/actor.h +++ b/src/actor.h @@ -12,8 +12,9 @@ typedef enum { typedef enum { ANIM_STAND, - ANIM_WALK, + // TODO: Put JUMP at end later ANIM_JUMP, + ANIM_WALK, ANIM_ATTACK, ANIM_HURT, ANIM_CLIMB, diff --git a/src/audio.c b/src/audio.c new file mode 100644 index 0000000..ee0f842 --- /dev/null +++ b/src/audio.c @@ -0,0 +1,14 @@ +#include "hugedriver.h" +#include "sdk/hardware.h" + +extern const hUGESong_t song_title; + +void audio_init(void) { + rAUDENA = 0x80; + rAUDTERM = 0xff; + rAUDVOL = 0x77; + + hUGE_init(&song_title); +} + +void audio_reset(void) { rAUDVOL = 0; } diff --git a/src/collision.c b/src/collision.c index 03dc1d8..6b33d3e 100644 --- a/src/collision.c +++ b/src/collision.c @@ -9,7 +9,8 @@ typedef enum { } coll_state_t; uint8_t can_move_to(uint8_t x, uint8_t y) { - const uint8_t coll = ((uint8_t*)MAP.collision_ptr)[y * MAP.map_width + x]; + const uint8_t coll = + ((uint8_t*)MAP.collision_ptr)[y * (MAP.map_width + 1) + x]; return coll & COLLF_WALK; } @@ -23,12 +24,12 @@ uint8_t get_tile_coord(uint8_t pixel_coord) { uint8_t player_bg_collides(void) { const int8_t map_x = PLAYER.x / 8 + MAP.camera_x; - if (map_x < 0 || map_x >= MAP.map_width) { + if (map_x < 0 || map_x + 1 >= MAP.map_width) { return 1; } - const int8_t map_y = PLAYER.y / 8 + MAP.camera_y; - if (map_y < 0 || map_y >= MAP.map_height) { + const int8_t map_y = PLAYER.y_hi / 8 + MAP.camera_y; + if (map_y < 0 || map_y + 1 >= MAP.map_height) { return 1; } @@ -38,8 +39,15 @@ uint8_t player_bg_collides(void) { } uint8_t player_in_air(void) { - const uint8_t x = get_tile_coord(PLAYER.x) + MAP.camera_x; - const uint8_t y = get_tile_coord(PLAYER.y) + MAP.camera_y; + const int8_t x = get_tile_coord(PLAYER.x) + MAP.camera_x; + if (x < 0 || x + 1 >= MAP.map_width) { + return 0; + } + + const int8_t y = get_tile_coord(PLAYER.y_hi) + MAP.camera_y; + if (y < 0 || y + 2 >= MAP.map_height) { + return 0; + } return can_move_to(x, y + 2) && can_move_to(x + 1, y + 2); } diff --git a/src/game.h b/src/game.h index f159ba5..c8114e1 100644 --- a/src/game.h +++ b/src/game.h @@ -80,4 +80,8 @@ uint8_t player_bg_collides(void); uint8_t player_in_air(void); +void audio_init(void); + +void audio_reset(void); + #endif diff --git a/src/hardware.inc b/src/hardware.inc new file mode 100644 index 0000000..d676af7 --- /dev/null +++ b/src/hardware.inc @@ -0,0 +1,913 @@ +;* +;* Gameboy Hardware definitions +;* +;* Based on Jones' hardware.inc +;* And based on Carsten Sorensen's ideas. +;* +;* Rev 1.1 - 15-Jul-97 : Added define check +;* Rev 1.2 - 18-Jul-97 : Added revision check macro +;* Rev 1.3 - 19-Jul-97 : Modified for RGBASM V1.05 +;* Rev 1.4 - 27-Jul-97 : Modified for new subroutine prefixes +;* Rev 1.5 - 15-Aug-97 : Added _HRAM, PAD, CART defines +;* : and Nintendo Logo +;* Rev 1.6 - 30-Nov-97 : Added rDIV, rTIMA, rTMA, & rTAC +;* Rev 1.7 - 31-Jan-98 : Added _SCRN0, _SCRN1 +;* Rev 1.8 - 15-Feb-98 : Added rSB, rSC +;* Rev 1.9 - 16-Feb-98 : Converted I/O registers to $FFXX format +;* Rev 2.0 - : Added GBC registers +;* Rev 2.1 - : Added MBC5 & cart RAM enable/disable defines +;* Rev 2.2 - : Fixed NR42,NR43, & NR44 equates +;* Rev 2.3 - : Fixed incorrect _HRAM equate +;* Rev 2.4 - 27-Apr-13 : Added some cart defines (AntonioND) +;* Rev 2.5 - 03-May-15 : Fixed format (AntonioND) +;* Rev 2.6 - 09-Apr-16 : Added GBC OAM and cart defines (AntonioND) +;* Rev 2.7 - 19-Jan-19 : Added rPCMXX (ISSOtm) +;* Rev 2.8 - 03-Feb-19 : Added audio registers flags (�lvaro Cuesta) +;* Rev 2.9 - 28-Feb-20 : Added utility rP1 constants +;* Rev 3.0 - 27-Aug-20 : Register ordering, byte-based sizes, OAM additions, general cleanup (Blitter Object) + +; If all of these are already defined, don't do it again. + +IF !DEF(HARDWARE_INC) +HARDWARE_INC SET 1 + +rev_Check_hardware_inc : MACRO +;NOTE: REVISION NUMBER CHANGES MUST BE ADDED +;TO SECOND PARAMETER IN FOLLOWING LINE. + IF \1 > 3.0 ;PUT REVISION NUMBER HERE + WARN "Version \1 or later of 'hardware.inc' is required." + ENDC +ENDM + +_VRAM EQU $8000 ; $8000->$9FFF +_VRAM8000 EQU _VRAM +_VRAM8800 EQU _VRAM+$800 +_VRAM9000 EQU _VRAM+$1000 +_SCRN0 EQU $9800 ; $9800->$9BFF +_SCRN1 EQU $9C00 ; $9C00->$9FFF +_SRAM EQU $A000 ; $A000->$BFFF +_RAM EQU $C000 ; $C000->$CFFF / $C000->$DFFF +_RAMBANK EQU $D000 ; $D000->$DFFF +_OAMRAM EQU $FE00 ; $FE00->$FE9F +_IO EQU $FF00 ; $FF00->$FF7F,$FFFF +_AUD3WAVERAM EQU $FF30 ; $FF30->$FF3F +_HRAM EQU $FF80 ; $FF80->$FFFE + +; *** MBC5 Equates *** + +rRAMG EQU $0000 ; $0000->$1fff +rROMB0 EQU $2000 ; $2000->$2fff +rROMB1 EQU $3000 ; $3000->$3fff - If more than 256 ROM banks are present. +rRAMB EQU $4000 ; $4000->$5fff - Bit 3 enables rumble (if present) + + +;*************************************************************************** +;* +;* Custom registers +;* +;*************************************************************************** + +; -- +; -- P1 ($FF00) +; -- Register for reading joy pad info. (R/W) +; -- +rP1 EQU $FF00 + +P1F_5 EQU %00100000 ; P15 out port, set to 0 to get buttons +P1F_4 EQU %00010000 ; P14 out port, set to 0 to get dpad +P1F_3 EQU %00001000 ; P13 in port +P1F_2 EQU %00000100 ; P12 in port +P1F_1 EQU %00000010 ; P11 in port +P1F_0 EQU %00000001 ; P10 in port + +P1F_GET_DPAD EQU P1F_5 +P1F_GET_BTN EQU P1F_4 +P1F_GET_NONE EQU P1F_4 | P1F_5 + + +; -- +; -- SB ($FF01) +; -- Serial Transfer Data (R/W) +; -- +rSB EQU $FF01 + + +; -- +; -- SC ($FF02) +; -- Serial I/O Control (R/W) +; -- +rSC EQU $FF02 + + +; -- +; -- DIV ($FF04) +; -- Divider register (R/W) +; -- +rDIV EQU $FF04 + + +; -- +; -- TIMA ($FF05) +; -- Timer counter (R/W) +; -- +rTIMA EQU $FF05 + + +; -- +; -- TMA ($FF06) +; -- Timer modulo (R/W) +; -- +rTMA EQU $FF06 + + +; -- +; -- TAC ($FF07) +; -- Timer control (R/W) +; -- +rTAC EQU $FF07 + +TACF_START EQU %00000100 +TACF_STOP EQU %00000000 +TACF_4KHZ EQU %00000000 +TACF_16KHZ EQU %00000011 +TACF_65KHZ EQU %00000010 +TACF_262KHZ EQU %00000001 + + +; -- +; -- IF ($FF0F) +; -- Interrupt Flag (R/W) +; -- +rIF EQU $FF0F + + +; -- +; -- AUD1SWEEP/NR10 ($FF10) +; -- Sweep register (R/W) +; -- +; -- Bit 6-4 - Sweep Time +; -- Bit 3 - Sweep Increase/Decrease +; -- 0: Addition (frequency increases???) +; -- 1: Subtraction (frequency increases???) +; -- Bit 2-0 - Number of sweep shift (# 0-7) +; -- Sweep Time: (n*7.8ms) +; -- +rNR10 EQU $FF10 +rAUD1SWEEP EQU rNR10 + +AUD1SWEEP_UP EQU %00000000 +AUD1SWEEP_DOWN EQU %00001000 + + +; -- +; -- AUD1LEN/NR11 ($FF11) +; -- Sound length/Wave pattern duty (R/W) +; -- +; -- Bit 7-6 - Wave Pattern Duty (00:12.5% 01:25% 10:50% 11:75%) +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +rNR11 EQU $FF11 +rAUD1LEN EQU rNR11 + + +; -- +; -- AUD1ENV/NR12 ($FF12) +; -- Envelope (R/W) +; -- +; -- Bit 7-4 - Initial value of envelope +; -- Bit 3 - Envelope UP/DOWN +; -- 0: Decrease +; -- 1: Range of increase +; -- Bit 2-0 - Number of envelope sweep (# 0-7) +; -- +rNR12 EQU $FF12 +rAUD1ENV EQU rNR12 + + +; -- +; -- AUD1LOW/NR13 ($FF13) +; -- Frequency low byte (W) +; -- +rNR13 EQU $FF13 +rAUD1LOW EQU rNR13 + + +; -- +; -- AUD1HIGH/NR14 ($FF14) +; -- Frequency high byte (W) +; -- +; -- Bit 7 - Initial (when set, sound restarts) +; -- Bit 6 - Counter/consecutive selection +; -- Bit 2-0 - Frequency's higher 3 bits +; -- +rNR14 EQU $FF14 +rAUD1HIGH EQU rNR14 + + +; -- +; -- AUD2LEN/NR21 ($FF16) +; -- Sound Length; Wave Pattern Duty (R/W) +; -- +; -- see AUD1LEN for info +; -- +rNR21 EQU $FF16 +rAUD2LEN EQU rNR21 + + +; -- +; -- AUD2ENV/NR22 ($FF17) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +rNR22 EQU $FF17 +rAUD2ENV EQU rNR22 + + +; -- +; -- AUD2LOW/NR23 ($FF18) +; -- Frequency low byte (W) +; -- +rNR23 EQU $FF18 +rAUD2LOW EQU rNR23 + + +; -- +; -- AUD2HIGH/NR24 ($FF19) +; -- Frequency high byte (W) +; -- +; -- see AUD1HIGH for info +; -- +rNR24 EQU $FF19 +rAUD2HIGH EQU rNR24 + + +; -- +; -- AUD3ENA/NR30 ($FF1A) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - Sound ON/OFF (1=ON,0=OFF) +; -- +rNR30 EQU $FF1A +rAUD3ENA EQU rNR30 + + +; -- +; -- AUD3LEN/NR31 ($FF1B) +; -- Sound length (R/W) +; -- +; -- Bit 7-0 - Sound length +; -- +rNR31 EQU $FF1B +rAUD3LEN EQU rNR31 + + +; -- +; -- AUD3LEVEL/NR32 ($FF1C) +; -- Select output level +; -- +; -- Bit 6-5 - Select output level +; -- 00: 0/1 (mute) +; -- 01: 1/1 +; -- 10: 1/2 +; -- 11: 1/4 +; -- +rNR32 EQU $FF1C +rAUD3LEVEL EQU rNR32 + + +; -- +; -- AUD3LOW/NR33 ($FF1D) +; -- Frequency low byte (W) +; -- +; -- see AUD1LOW for info +; -- +rNR33 EQU $FF1D +rAUD3LOW EQU rNR33 + + +; -- +; -- AUD3HIGH/NR34 ($FF1E) +; -- Frequency high byte (W) +; -- +; -- see AUD1HIGH for info +; -- +rNR34 EQU $FF1E +rAUD3HIGH EQU rNR34 + + +; -- +; -- AUD4LEN/NR41 ($FF20) +; -- Sound length (R/W) +; -- +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +rNR41 EQU $FF20 +rAUD4LEN EQU rNR41 + + +; -- +; -- AUD4ENV/NR42 ($FF21) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +rNR42 EQU $FF21 +rAUD4ENV EQU rNR42 + + +; -- +; -- AUD4POLY/NR43 ($FF22) +; -- Polynomial counter (R/W) +; -- +; -- Bit 7-4 - Selection of the shift clock frequency of the (scf) +; -- polynomial counter (0000-1101) +; -- freq=drf*1/2^scf (not sure) +; -- Bit 3 - Selection of the polynomial counter's step +; -- 0: 15 steps +; -- 1: 7 steps +; -- Bit 2-0 - Selection of the dividing ratio of frequencies (drf) +; -- 000: f/4 001: f/8 010: f/16 011: f/24 +; -- 100: f/32 101: f/40 110: f/48 111: f/56 (f=4.194304 Mhz) +; -- +rNR43 EQU $FF22 +rAUD4POLY EQU rNR43 + + +; -- +; -- AUD4GO/NR44 ($FF23) +; -- +; -- Bit 7 - Inital +; -- Bit 6 - Counter/consecutive selection +; -- +rNR44 EQU $FF23 +rAUD4GO EQU rNR44 + + +; -- +; -- AUDVOL/NR50 ($FF24) +; -- Channel control / ON-OFF / Volume (R/W) +; -- +; -- Bit 7 - Vin->SO2 ON/OFF (Vin??) +; -- Bit 6-4 - SO2 output level (volume) (# 0-7) +; -- Bit 3 - Vin->SO1 ON/OFF (Vin??) +; -- Bit 2-0 - SO1 output level (volume) (# 0-7) +; -- +rNR50 EQU $FF24 +rAUDVOL EQU rNR50 + +AUDVOL_VIN_LEFT EQU %10000000 ; SO2 +AUDVOL_VIN_RIGHT EQU %00001000 ; SO1 + + +; -- +; -- AUDTERM/NR51 ($FF25) +; -- Selection of Sound output terminal (R/W) +; -- +; -- Bit 7 - Output sound 4 to SO2 terminal +; -- Bit 6 - Output sound 3 to SO2 terminal +; -- Bit 5 - Output sound 2 to SO2 terminal +; -- Bit 4 - Output sound 1 to SO2 terminal +; -- Bit 3 - Output sound 4 to SO1 terminal +; -- Bit 2 - Output sound 3 to SO1 terminal +; -- Bit 1 - Output sound 2 to SO1 terminal +; -- Bit 0 - Output sound 0 to SO1 terminal +; -- +rNR51 EQU $FF25 +rAUDTERM EQU rNR51 + +; SO2 +AUDTERM_4_LEFT EQU %10000000 +AUDTERM_3_LEFT EQU %01000000 +AUDTERM_2_LEFT EQU %00100000 +AUDTERM_1_LEFT EQU %00010000 +; SO1 +AUDTERM_4_RIGHT EQU %00001000 +AUDTERM_3_RIGHT EQU %00000100 +AUDTERM_2_RIGHT EQU %00000010 +AUDTERM_1_RIGHT EQU %00000001 + + +; -- +; -- AUDENA/NR52 ($FF26) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - All sound on/off (sets all audio regs to 0!) +; -- Bit 3 - Sound 4 ON flag (read only) +; -- Bit 2 - Sound 3 ON flag (read only) +; -- Bit 1 - Sound 2 ON flag (read only) +; -- Bit 0 - Sound 1 ON flag (read only) +; -- +rNR52 EQU $FF26 +rAUDENA EQU rNR52 + +AUDENA_ON EQU %10000000 +AUDENA_OFF EQU %00000000 ; sets all audio regs to 0! + + +; -- +; -- LCDC ($FF40) +; -- LCD Control (R/W) +; -- +rLCDC EQU $FF40 + +LCDCF_OFF EQU %00000000 ; LCD Control Operation +LCDCF_ON EQU %10000000 ; LCD Control Operation +LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select +LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select +LCDCF_WINOFF EQU %00000000 ; Window Display +LCDCF_WINON EQU %00100000 ; Window Display +LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select +LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select +LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select +LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select +LCDCF_OBJ8 EQU %00000000 ; OBJ Construction +LCDCF_OBJ16 EQU %00000100 ; OBJ Construction +LCDCF_OBJOFF EQU %00000000 ; OBJ Display +LCDCF_OBJON EQU %00000010 ; OBJ Display +LCDCF_BGOFF EQU %00000000 ; BG Display +LCDCF_BGON EQU %00000001 ; BG Display +; "Window Character Data Select" follows BG + + +; -- +; -- STAT ($FF41) +; -- LCDC Status (R/W) +; -- +rSTAT EQU $FF41 + +STATF_LYC EQU %01000000 ; LYC=LY Coincidence (Selectable) +STATF_MODE10 EQU %00100000 ; Mode 10 +STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank) +STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank) +STATF_LYCF EQU %00000100 ; Coincidence Flag +STATF_HBL EQU %00000000 ; H-Blank +STATF_VBL EQU %00000001 ; V-Blank +STATF_OAM EQU %00000010 ; OAM-RAM is used by system +STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system +STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe + + +; -- +; -- SCY ($FF42) +; -- Scroll Y (R/W) +; -- +rSCY EQU $FF42 + + +; -- +; -- SCX ($FF43) +; -- Scroll X (R/W) +; -- +rSCX EQU $FF43 + + +; -- +; -- LY ($FF44) +; -- LCDC Y-Coordinate (R) +; -- +; -- Values range from 0->153. 144->153 is the VBlank period. +; -- +rLY EQU $FF44 + + +; -- +; -- LYC ($FF45) +; -- LY Compare (R/W) +; -- +; -- When LY==LYC, STATF_LYCF will be set in STAT +; -- +rLYC EQU $FF45 + + +; -- +; -- DMA ($FF46) +; -- DMA Transfer and Start Address (W) +; -- +rDMA EQU $FF46 + + +; -- +; -- BGP ($FF47) +; -- BG Palette Data (W) +; -- +; -- Bit 7-6 - Intensity for %11 +; -- Bit 5-4 - Intensity for %10 +; -- Bit 3-2 - Intensity for %01 +; -- Bit 1-0 - Intensity for %00 +; -- +rBGP EQU $FF47 + + +; -- +; -- OBP0 ($FF48) +; -- Object Palette 0 Data (W) +; -- +; -- See BGP for info +; -- +rOBP0 EQU $FF48 + + +; -- +; -- OBP1 ($FF49) +; -- Object Palette 1 Data (W) +; -- +; -- See BGP for info +; -- +rOBP1 EQU $FF49 + + +; -- +; -- WY ($FF4A) +; -- Window Y Position (R/W) +; -- +; -- 0 <= WY <= 143 +; -- When WY = 0, the window is displayed from the top edge of the LCD screen. +; -- +rWY EQU $FF4A + + +; -- +; -- WX ($FF4B) +; -- Window X Position (R/W) +; -- +; -- 7 <= WX <= 166 +; -- When WX = 7, the window is displayed from the left edge of the LCD screen. +; -- Values of 0-6 and 166 are unreliable due to hardware bugs. +; -- +rWX EQU $FF4B + + +; -- +; -- SPEED ($FF4D) +; -- Select CPU Speed (R/W) +; -- +rKEY1 EQU $FF4D +rSPD EQU rKEY1 + +KEY1F_DBLSPEED EQU %10000000 ; 0=Normal Speed, 1=Double Speed (R) +KEY1F_PREPARE EQU %00000001 ; 0=No, 1=Prepare (R/W) + + +; -- +; -- VBK ($FF4F) +; -- Select Video RAM Bank (R/W) +; -- +; -- Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1) +; -- +rVBK EQU $FF4F + + +; -- +; -- HDMA1 ($FF51) +; -- High byte for Horizontal Blanking/General Purpose DMA source address (W) +; -- CGB Mode Only +; -- +rHDMA1 EQU $FF51 + + +; -- +; -- HDMA2 ($FF52) +; -- Low byte for Horizontal Blanking/General Purpose DMA source address (W) +; -- CGB Mode Only +; -- +rHDMA2 EQU $FF52 + + +; -- +; -- HDMA3 ($FF53) +; -- High byte for Horizontal Blanking/General Purpose DMA destination address (W) +; -- CGB Mode Only +; -- +rHDMA3 EQU $FF53 + + +; -- +; -- HDMA4 ($FF54) +; -- Low byte for Horizontal Blanking/General Purpose DMA destination address (W) +; -- CGB Mode Only +; -- +rHDMA4 EQU $FF54 + + +; -- +; -- HDMA5 ($FF55) +; -- Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W) +; -- CGB Mode Only +; -- +rHDMA5 EQU $FF55 + +HDMA5F_MODE_GP EQU %00000000 ; General Purpose DMA (W) +HDMA5F_MODE_HBL EQU %10000000 ; HBlank DMA (W) + +; -- Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete +HDMA5F_BUSY EQU %10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R) + + +; -- +; -- RP ($FF56) +; -- Infrared Communications Port (R/W) +; -- CGB Mode Only +; -- +rRP EQU $FF56 + +RPF_ENREAD EQU %11000000 +RPF_DATAIN EQU %00000010 ; 0=Receiving IR Signal, 1=Normal +RPF_WRITE_HI EQU %00000001 +RPF_WRITE_LO EQU %00000000 + + +; -- +; -- BCPS ($FF68) +; -- Background Color Palette Specification (R/W) +; -- +rBCPS EQU $FF68 + +BCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) + + +; -- +; -- BCPD ($FF69) +; -- Background Color Palette Data (R/W) +; -- +rBCPD EQU $FF69 + + +; -- +; -- OCPS ($FF6A) +; -- Object Color Palette Specification (R/W) +; -- +rOCPS EQU $FF6A + +OCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) + + +; -- +; -- OCPD ($FF6B) +; -- Object Color Palette Data (R/W) +; -- +rOCPD EQU $FF6B + + +; -- +; -- SMBK/SVBK ($FF70) +; -- Select Main RAM Bank (R/W) +; -- +; -- Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7) +; -- +rSVBK EQU $FF70 +rSMBK EQU rSVBK + + +; -- +; -- PCM12 ($FF76) +; -- Sound channel 1&2 PCM amplitude (R) +; -- +; -- Bit 7-4 - Copy of sound channel 2's PCM amplitude +; -- Bit 3-0 - Copy of sound channel 1's PCM amplitude +; -- +rPCM12 EQU $FF76 + + +; -- +; -- PCM34 ($FF77) +; -- Sound channel 3&4 PCM amplitude (R) +; -- +; -- Bit 7-4 - Copy of sound channel 4's PCM amplitude +; -- Bit 3-0 - Copy of sound channel 3's PCM amplitude +; -- +rPCM34 EQU $FF77 + + +; -- +; -- IE ($FFFF) +; -- Interrupt Enable (R/W) +; -- +rIE EQU $FFFF + +IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13 +IEF_SERIAL EQU %00001000 ; Serial I/O transfer end +IEF_TIMER EQU %00000100 ; Timer Overflow +IEF_LCDC EQU %00000010 ; LCDC (see STAT) +IEF_VBLANK EQU %00000001 ; V-Blank + + +;*************************************************************************** +;* +;* Flags common to multiple sound channels +;* +;*************************************************************************** + +; -- +; -- Square wave duty cycle +; -- +; -- Can be used with AUD1LEN and AUD2LEN +; -- See AUD1LEN for more info +; -- +AUDLEN_DUTY_12_5 EQU %00000000 ; 12.5% +AUDLEN_DUTY_25 EQU %01000000 ; 25% +AUDLEN_DUTY_50 EQU %10000000 ; 50% +AUDLEN_DUTY_75 EQU %11000000 ; 75% + + +; -- +; -- Audio envelope flags +; -- +; -- Can be used with AUD1ENV, AUD2ENV, AUD4ENV +; -- See AUD1ENV for more info +; -- +AUDENV_UP EQU %00001000 +AUDENV_DOWN EQU %00000000 + + +; -- +; -- Audio trigger flags +; -- +; -- Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH +; -- See AUD1HIGH for more info +; -- + +AUDHIGH_RESTART EQU %10000000 +AUDHIGH_LENGTH_ON EQU %01000000 +AUDHIGH_LENGTH_OFF EQU %00000000 + + +;*************************************************************************** +;* +;* CPU values on bootup (a=type, b=qualifier) +;* +;*************************************************************************** + +BOOTUP_A_DMG EQU $01 ; Dot Matrix Game +BOOTUP_A_CGB EQU $11 ; Color GameBoy +BOOTUP_A_MGB EQU $FF ; Mini GameBoy (Pocket GameBoy) + +; if a=BOOTUP_A_CGB, bit 0 in b can be checked to determine if real CGB or +; other system running in GBC mode +BOOTUP_B_CGB EQU %00000000 +BOOTUP_B_AGB EQU %00000001 ; GBA, GBA SP, Game Boy Player, or New GBA SP + + +;*************************************************************************** +;* +;* Cart related +;* +;*************************************************************************** + +; $0143 Color GameBoy compatibility code +CART_COMPATIBLE_DMG EQU $00 +CART_COMPATIBLE_DMG_GBC EQU $80 +CART_COMPATIBLE_GBC EQU $C0 + +; $0146 GameBoy/Super GameBoy indicator +CART_INDICATOR_GB EQU $00 +CART_INDICATOR_SGB EQU $03 + +; $0147 Cartridge type +CART_ROM EQU $00 +CART_ROM_MBC1 EQU $01 +CART_ROM_MBC1_RAM EQU $02 +CART_ROM_MBC1_RAM_BAT EQU $03 +CART_ROM_MBC2 EQU $05 +CART_ROM_MBC2_BAT EQU $06 +CART_ROM_RAM EQU $08 +CART_ROM_RAM_BAT EQU $09 +CART_ROM_MMM01 EQU $0B +CART_ROM_MMM01_RAM EQU $0C +CART_ROM_MMM01_RAM_BAT EQU $0D +CART_ROM_MBC3_BAT_RTC EQU $0F +CART_ROM_MBC3_RAM_BAT_RTC EQU $10 +CART_ROM_MBC3 EQU $11 +CART_ROM_MBC3_RAM EQU $12 +CART_ROM_MBC3_RAM_BAT EQU $13 +CART_ROM_MBC5 EQU $19 +CART_ROM_MBC5_BAT EQU $1A +CART_ROM_MBC5_RAM_BAT EQU $1B +CART_ROM_MBC5_RUMBLE EQU $1C +CART_ROM_MBC5_RAM_RUMBLE EQU $1D +CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E +CART_ROM_MBC7_RAM_BAT_GYRO EQU $22 +CART_ROM_POCKET_CAMERA EQU $FC +CART_ROM_BANDAI_TAMA5 EQU $FD +CART_ROM_HUDSON_HUC3 EQU $FE +CART_ROM_HUDSON_HUC1 EQU $FF + +; $0148 ROM size +; these are kilobytes +CART_ROM_32KB EQU $00 ; 2 banks +CART_ROM_64KB EQU $01 ; 4 banks +CART_ROM_128KB EQU $02 ; 8 banks +CART_ROM_256KB EQU $03 ; 16 banks +CART_ROM_512KB EQU $04 ; 32 banks +CART_ROM_1024KB EQU $05 ; 64 banks +CART_ROM_2048KB EQU $06 ; 128 banks +CART_ROM_4096KB EQU $07 ; 256 banks +CART_ROM_8192KB EQU $08 ; 512 banks +CART_ROM_1152KB EQU $52 ; 72 banks +CART_ROM_1280KB EQU $53 ; 80 banks +CART_ROM_1536KB EQU $54 ; 96 banks + +; $0149 SRAM size +; these are kilobytes +CART_SRAM_NONE EQU 0 +CART_SRAM_2KB EQU 1 ; 1 incomplete bank +CART_SRAM_8KB EQU 2 ; 1 bank +CART_SRAM_32KB EQU 3 ; 4 banks +CART_SRAM_128KB EQU 4 ; 16 banks + +CART_SRAM_ENABLE EQU $0A +CART_SRAM_DISABLE EQU $00 + +; $014A Destination code +CART_DEST_JAPANESE EQU $00 +CART_DEST_NON_JAPANESE EQU $01 + + +;*************************************************************************** +;* +;* Keypad related +;* +;*************************************************************************** + +PADF_DOWN EQU $80 +PADF_UP EQU $40 +PADF_LEFT EQU $20 +PADF_RIGHT EQU $10 +PADF_START EQU $08 +PADF_SELECT EQU $04 +PADF_B EQU $02 +PADF_A EQU $01 + +PADB_DOWN EQU $7 +PADB_UP EQU $6 +PADB_LEFT EQU $5 +PADB_RIGHT EQU $4 +PADB_START EQU $3 +PADB_SELECT EQU $2 +PADB_B EQU $1 +PADB_A EQU $0 + + +;*************************************************************************** +;* +;* Screen related +;* +;*************************************************************************** + +SCRN_X EQU 160 ; Width of screen in pixels +SCRN_Y EQU 144 ; Height of screen in pixels +SCRN_X_B EQU 20 ; Width of screen in bytes +SCRN_Y_B EQU 18 ; Height of screen in bytes + +SCRN_VX EQU 256 ; Virtual width of screen in pixels +SCRN_VY EQU 256 ; Virtual height of screen in pixels +SCRN_VX_B EQU 32 ; Virtual width of screen in bytes +SCRN_VY_B EQU 32 ; Virtual height of screen in bytes + + +;*************************************************************************** +;* +;* OAM related +;* +;*************************************************************************** + +; OAM attributes +; each entry in OAM RAM is 4 bytes (sizeof_OAM_ATTRS) +RSRESET +OAMA_Y RB 1 ; y pos +OAMA_X RB 1 ; x pos +OAMA_TILEID RB 1 ; tile id +OAMA_FLAGS RB 1 ; flags (see below) +sizeof_OAM_ATTRS RB 0 + +OAM_COUNT EQU 40 ; number of OAM entries in OAM RAM + +; flags +OAMF_PRI EQU %10000000 ; Priority +OAMF_YFLIP EQU %01000000 ; Y flip +OAMF_XFLIP EQU %00100000 ; X flip +OAMF_PAL0 EQU %00000000 ; Palette number; 0,1 (DMG) +OAMF_PAL1 EQU %00010000 ; Palette number; 0,1 (DMG) +OAMF_BANK0 EQU %00000000 ; Bank number; 0,1 (GBC) +OAMF_BANK1 EQU %00001000 ; Bank number; 0,1 (GBC) + +OAMF_PALMASK EQU %00000111 ; Palette (GBC) + +OAMB_PRI EQU 7 ; Priority +OAMB_YFLIP EQU 6 ; Y flip +OAMB_XFLIP EQU 5 ; X flip +OAMB_PAL1 EQU 4 ; Palette number; 0,1 (DMG) +OAMB_BANK1 EQU 3 ; Bank number; 0,1 (GBC) + + +;* +;* Nintendo scrolling logo +;* (Code won't work on a real GameBoy) +;* (if next lines are altered.) +NINTENDO_LOGO : MACRO + DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D + DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99 + DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E +ENDM + + ENDC ;HARDWARE_INC diff --git a/src/helpers.asm b/src/helpers.asm index df4ff77..7dfd7f8 100644 --- a/src/helpers.asm +++ b/src/helpers.asm @@ -1,3 +1,11 @@ +SECTION "LCD controller status interrupt", ROM0[$0048] + ;; HACK!!!!!!!!!!!!! + ;; there's some sort of bug in the emulator which needs to be fixed, + ;; which screws up the program counter immediately after it exits a halt. + ;; this nop protects against that for now. + nop + jp isr_wrapper + SECTION "vblank", ROM0[$40] handle_vblank: @@ -12,3 +20,21 @@ _interrupts_disable:: _interrupts_enable:: ei ret + +SECTION "hUGE ISR", ROM0 + +isr_wrapper: + ld a, [_game_state] + cp 0 + jr nz, .done + push af + push hl + push bc + push de + call hUGE_dosound + pop de + pop bc + pop hl + pop af +.done: + reti diff --git a/src/huge.inc b/src/huge.inc new file mode 100644 index 0000000..9c728e8 --- /dev/null +++ b/src/huge.inc @@ -0,0 +1,81 @@ +dn: MACRO ;; (note, instr, effect) + db \1 + db ((\2 << 4) | (\3 >> 8)) + db LOW(\3) +ENDM + +rsreset +def C_3 rb +def C#3 rb +def D_3 rb +def D#3 rb +def E_3 rb +def F_3 rb +def F#3 rb +def G_3 rb +def G#3 rb +def A_3 rb +def A#3 rb +def B_3 rb +def C_4 rb +def C#4 rb +def D_4 rb +def D#4 rb +def E_4 rb +def F_4 rb +def F#4 rb +def G_4 rb +def G#4 rb +def A_4 rb +def A#4 rb +def B_4 rb +def C_5 rb +def C#5 rb +def D_5 rb +def D#5 rb +def E_5 rb +def F_5 rb +def F#5 rb +def G_5 rb +def G#5 rb +def A_5 rb +def A#5 rb +def B_5 rb +def C_6 rb +def C#6 rb +def D_6 rb +def D#6 rb +def E_6 rb +def F_6 rb +def F#6 rb +def G_6 rb +def G#6 rb +def A_6 rb +def A#6 rb +def B_6 rb +def C_7 rb +def C#7 rb +def D_7 rb +def D#7 rb +def E_7 rb +def F_7 rb +def F#7 rb +def G_7 rb +def G#7 rb +def A_7 rb +def A#7 rb +def B_7 rb +def C_8 rb +def C#8 rb +def D_8 rb +def D#8 rb +def E_8 rb +def F_8 rb +def F#8 rb +def G_8 rb +def G#8 rb +def A_8 rb +def A#8 rb +def B_8 rb +def LAST_NOTE rb +___ EQU 90 ; the default "no note" value diff --git a/src/huge_note_table.inc b/src/huge_note_table.inc new file mode 100644 index 0000000..051e3b0 --- /dev/null +++ b/src/huge_note_table.inc @@ -0,0 +1,78 @@ +;; hUGETracker note table. +;; Written by SuperDisk 2019 + +;; Gameboy sound frequences are represented as 11 byte periods. +;; this note table was generated from http://www.devrs.com/gb/files/sndtab.html + +dw 44 +dw 156 +dw 262 +dw 363 +dw 457 +dw 547 +dw 631 +dw 710 +dw 786 +dw 854 +dw 923 +dw 986 +dw 1046 +dw 1102 +dw 1155 +dw 1205 +dw 1253 +dw 1297 +dw 1339 +dw 1379 +dw 1417 +dw 1452 +dw 1486 +dw 1517 +dw 1546 +dw 1575 +dw 1602 +dw 1627 +dw 1650 +dw 1673 +dw 1694 +dw 1714 +dw 1732 +dw 1750 +dw 1767 +dw 1783 +dw 1798 +dw 1812 +dw 1825 +dw 1837 +dw 1849 +dw 1860 +dw 1871 +dw 1881 +dw 1890 +dw 1899 +dw 1907 +dw 1915 +dw 1923 +dw 1930 +dw 1936 +dw 1943 +dw 1949 +dw 1954 +dw 1959 +dw 1964 +dw 1969 +dw 1974 +dw 1978 +dw 1982 +dw 1985 +dw 1988 +dw 1992 +dw 1995 +dw 1998 +dw 2001 +dw 2004 +dw 2006 +dw 2009 +dw 2011 +dw 2013 +dw 2015 diff --git a/src/hugedriver.asm b/src/hugedriver.asm new file mode 100644 index 0000000..7ef476d --- /dev/null +++ b/src/hugedriver.asm @@ -0,0 +1,1729 @@ +include "hardware.inc" +include "huge.inc" + +add_a_to_r16: MACRO + add \2 + ld \2, a + adc \1 + sub \2 + ld \1, a +ENDM + +add_a_to_hl: MACRO + add_a_to_r16 h, l +ENDM + +add_a_to_de: MACRO + add_a_to_r16 d, e +ENDM + +ret_dont_call_playnote: MACRO + pop hl + pop af + and a ; Clear carry to avoid calling `play_chX_note` + push af + jp hl +ENDM + +add_a_ind_ret_hl: MACRO + ld hl, \1 + add [hl] + inc hl + ld h, [hl] + ld l, a + adc h + sub l + ld h, a +ENDM + +load_hl_ind: MACRO + ld hl, \1 + ld a, [hl+] + ld h, [hl] + ld l, a +ENDM + +load_de_ind: MACRO + ld a, [\1] + ld e, a + ld a, [\1+1] + ld d, a +ENDM + +retMute: MACRO + bit \1, a + ret nz +ENDM + +checkMute: MACRO + ld a, [mute_channels] + bit \1, a + jr nz, \2 +ENDM + +;; Maximum pattern length +PATTERN_LENGTH EQU 64 +;; Amount to be shifted in order to skip a channel. +CHANNEL_SIZE_EXPONENT EQU 3 + +SECTION "Playback variables", WRAM0 +;; Active song descriptor +order_cnt: db +_start_song_descriptor_pointers: +;; Pointers to the song's current four orders (one per channel) +order1: dw +order2: dw +order3: dw +order4: dw + +;; Pointers to the instrument tables +duty_instruments: dw +wave_instruments: dw +noise_instruments: dw + +;; Misc. pointers +routines: dw +waves: dw +_end_song_descriptor_pointers: + +;; Pointers to the current patterns (sort of a cache) +pattern1: dw +pattern2: dw +pattern3: dw +pattern4: dw + +;; How long a row lasts in ticks (1 = one row per call to `hUGE_dosound`, etc. 0 translates to 256) +ticks_per_row: db + +_hUGE_current_wave:: +hUGE_current_wave:: +;; ID of the wave currently loaded into wave RAM +current_wave: db +hUGE_NO_WAVE equ 100 + EXPORT hUGE_NO_WAVE + +;; Everything between this and `end_zero` is zero-initialized by `hUGE_init` +start_zero: + +mute_channels: db +current_order: db +next_order: db +row_break: db + +temp_note_value: dw +row: db +tick: db +counter: db + +channels: +;;;;;;;;;;; +;;Channel 1 +;;;;;;;;;;; +channel1: +channel_period1: dw +toneporta_target1: dw +channel_note1: db +vibrato_tremolo_phase1: db +envelope1: db +highmask1: db + +;;;;;;;;;;; +;;Channel 2 +;;;;;;;;;;; +channel2: +channel_period2: dw +toneporta_target2: dw +channel_note2: db +vibrato_tremolo_phase2: db +envelope2: db +highmask2: db + +;;;;;;;;;;; +;;Channel 3 +;;;;;;;;;;; +channel3: +channel_period3: dw +toneporta_target3: dw +channel_note3: db +vibrato_tremolo_phase3: db +envelope3: db +highmask3: db + +;;;;;;;;;;; +;;Channel 4 +;;;;;;;;;;; +channel4: +channel_period4: dw +toneporta_target4: dw +channel_note4: db +vibrato_tremolo_phase4: db +envelope4: db +highmask4: db + +end_zero: + +SECTION "Sound Driver", ROM0 + +;;; Sets up hUGEDriver to play a song. +;;; !!! BE SURE THAT `hUGE_dosound` WILL NOT BE CALLED WHILE THIS RUNS !!! +;;; Param: HL = Pointer to the "song descriptor" you wish to load (typically exported by hUGETracker). +;;; Destroys: AF C DE HL +_hUGE_init:: +hUGE_init:: + ld a, [hl+] ; tempo + ld [ticks_per_row], a + + ld a, [hl+] + ld e, a + ld a, [hl+] + ld d, a + ld a, [de] + ld [order_cnt], a + + ld c, _end_song_descriptor_pointers - (_start_song_descriptor_pointers) + ld de, order1 + +.copy_song_descriptor_loop: + ld a, [hl+] + ld [de], a + inc de + dec c + jr nz, .copy_song_descriptor_loop + +IF !DEF(PREVIEW_MODE) + ;; Zero some ram + ld c, end_zero - start_zero + ld hl, start_zero + xor a +.fill_loop: + ld [hl+], a + dec c + jr nz, .fill_loop +ENDC + + ;; These two are zero-initialized by the loop above, so these two writes must come after + ld a, %11110000 + ld [envelope1], a + ld [envelope2], a + + ;; Force loading the next wave + ld a, hUGE_NO_WAVE + ld [current_wave], a + +;; Preview mode needs to load the order ID from memory +IF !DEF(PREVIEW_MODE) + ld c, 0 +ELSE + ld a, [current_order] + ld c, a +ENDC + ;; fallthrough (load the pattern pointers) + +;;; Sets all 4 pattern pointers from a certain index in the respective 4 orders. +;;; Param: C = The index (in increments of 2) +;;; Destroy: AF DE HL +load_patterns: +IF DEF(PREVIEW_MODE) + db $fc ; signal order update to tracker +ENDC + + ld hl, order1 + ld de, pattern1 + call .load_pattern + + ld hl, order2 + call .load_pattern + + ld hl, order3 + call .load_pattern + + ld hl, order4 + ;; fallthrough + +.load_pattern: + ld a, [hl+] + add c + ld h, [hl] + ld l, a + adc h + sub l + ld h, a + + ld a, [hl+] + ld [de], a + inc de + ld a, [hl] + ld [de], a + inc de + ret + + +;;; Sets a channel's muting status. +;;; Muted channels are left entirely alone by the driver, so that you can repurpose them, +;;; for example for sound effects, CH3 sample playback, etc. +;;; If muting the channel, the note being played will be cut. +;;; Param: B = Which channel to enable; 0 for CH1, 1 for CH2, etc. +;;; Param: C = 0 to unmute the channel, 1 to mute it +;;; Destroy: A C E HL +hUGE_mute_channel:: + ld e, $fe + ld a, b + or a + jr z, .enable_cut +.enable_loop: + sla c + rlc e + dec a + jr nz, .enable_loop +.enable_cut: + ld a, [mute_channels] + and e + or c + ld [mute_channels], a + and c + jp nz, note_cut + ret + + +;;; Reads a pattern's current row. +;;; Param: BC = Pointer to the pattern +;;; Param: [row] = Index of the current ro< +;;; Return: A = Note ID +;;; Return: B = Instrument (upper nibble) & effect code (lower nibble) +;;; Return: C = Effect parameter +;;; Destroy: HL +get_current_row: + ld a, [row] + ld h, a + ;; Multiply by 3 for the note value + add h + add h + + ld h, 0 + ld l, a + add hl, bc ; HL now points at the 3rd byte of the note + ld a, [hl+] + ld b, [hl] + inc hl + ld c, [hl] + ret + +;;; Gets the "period" of a pattern's current note. +;;; Param: HL = Pointer to the pattern pointer +;;; Param: [row] = Index of the current row +;;; Param: DE = Location to write the note's index to, if applicable +;;; Return: HL = Note's period +;;; Return: CF = Set if and only if a "valid" note (i.e. not a "rest") +;;; Return: [DE] = Note's ID, not updated if a "rest" +;;; Return: B = Instrument (upper nibble) & effect code (lower nibble) +;;; Return: C = Effect parameter +;;; Destroy: AF +get_current_note: + ld a, [hl+] + ld c, a + ld b, [hl] + + call get_current_row + ld hl, 0 + + ;; If the note we found is greater than LAST_NOTE, then it's not a valid note + ;; and nothing needs to be updated. + cp LAST_NOTE + ret nc + + ;; Store the loaded note value in channel_noteX + ld [de], a + +;;; Gets a note's "period", i.e. what should be written to NRx3 and NRx4. +;;; Param: A = Note ID +;;; Return: HL = Note's period +;;; Return: CF = 1 +;;; Destroy: AF +get_note_period: + add a ;; double it to get index into hi/lo table + add LOW(note_table) + ld l, a + adc HIGH(note_table) + sub l + ld h, a + ld a, [hl+] + ld h, [hl] + ld l, a + + scf + ret + +;;; Gets a note's "polynomial counter", i.e. what should be written to NR44. +;;; Param: A = Note ID +;;; Return: A = Note's poly +;;; Destroy: F HL +get_note_poly: + ;; Invert the order of the numbers + add 192 ; (255 - 63) + cpl + + ;; Thanks to RichardULZ for this formula + ;; https://docs.google.com/spreadsheets/d/1O9OTAHgLk1SUt972w88uVHp44w7HKEbS/edit#gid=75028951 + ; if A > 7 then begin + ; B := (A-4) div 4; + ; C := (A mod 4)+4; + ; A := (C or (B shl 4)) + ; end; + + ; if A < 7 then return + cp 7 + ret c + + ld h, a + + ; B := (A-4) div 4; + sub 4 + srl a + srl a + ld l, a + + ; C := (A mod 4)+4; + ld a, h + and 3 ; mod 4 + add 4 + + ; A := (C or (B shl 4)) + swap l + or l + ret + + +;;; Computes the pointer to a member of a channel. +;;; Param: B = Which channel (0 = CH1, 1 = CH2, etc.) +;;; Param: D = Offset within the channel struct +;;; Return: HL = Pointer to the channel's member +;;; Destroy: AF +ptr_to_channel_member: + ld a, b +REPT CHANNEL_SIZE_EXPONENT + add a +ENDR + add d + ld hl, channels + add LOW(channels) + ld l, a + adc HIGH(channels) + sub l + ld h, a + ret + + +;;; Updates a channel's frequency, and possibly restarts it. +;;; Note that CH4 is *never* restarted by this! +;;; Param: B = Which channel to update (0 = CH1, 1 = CH2, etc.) +;;; Param: (ignored for CH4) A = ORed to the value written to NRx4 +;;; Param: (for CH4) E = Note ID +;;; Param: (otherwise) DE = Note period +;;; Destroy: AF B +;;; Destroy: (for CH4) HL +update_channel_freq: + ld c, a + ld a, [mute_channels] + dec b + jr z, .update_channel2 + dec b + jr z, .update_channel3 + dec b + jr z, .update_channel4 + +.update_channel1: + retMute 0 + + ld a, e + ldh [rAUD1LOW], a + ld a, d + or c + ldh [rAUD1HIGH], a + ret + +.update_channel2: + retMute 1 + + ld a, e + ldh [rAUD2LOW], a + ld a, d + or c + ldh [rAUD2HIGH], a + ret + +.update_channel3: + retMute 2 + + ld a, e + ldh [rAUD3LOW], a + ld a, d + or c + ldh [rAUD3HIGH], a + ret + +.update_channel4: + retMute 3 + + ld a, e + call get_note_poly + ldh [rAUD4POLY], a + xor a + ldh [rAUD4GO], a + ret + + +play_note_routines: + jr play_ch1_note + jr play_ch2_note + jr play_ch3_note + jr play_ch4_note + +play_ch1_note: + ld a, [mute_channels] + retMute 0 + + ;; Play a note on channel 1 (square wave) + ld a, [temp_note_value] + ld [channel_period1], a + ldh [rAUD1LOW], a + + ld a, [temp_note_value+1] + ld [channel_period1+1], a + + ;; Get the highmask and apply it. + ld hl, highmask1 + or [hl] + ldh [rAUD1HIGH], a + + ret + +play_ch2_note: + ld a, [mute_channels] + retMute 1 + + ;; Play a note on channel 2 (square wave) + ld a, [temp_note_value] + ld [channel_period2], a + ldh [rAUD2LOW], a + + ld a, [temp_note_value+1] + ld [channel_period2+1], a + + ;; Get the highmask and apply it. + ld hl, highmask2 + or [hl] + ldh [rAUD2HIGH], a + + ret + +play_ch3_note: + ld a, [mute_channels] + retMute 2 + + ;; Triggering CH3 while it's reading a byte corrupts wave RAM. + ;; To avoid this, we kill the wave channel (0 → NR30), then re-enable it. + ;; This way, CH3 will be paused when we trigger it by writing to NR34. + ;; TODO: what if `highmask3` bit 7 is not set, though? + xor a + ldh [rAUD3ENA], a + cpl + ldh [rAUD3ENA], a + + ;; Play a note on channel 3 (waveform) + ld a, [temp_note_value] + ld [channel_period3], a + ldh [rAUD3LOW], a + + ld a, [temp_note_value+1] + ld [channel_period3+1], a + + ;; Get the highmask and apply it. + ld hl, highmask3 + or [hl] + ldh [rAUD3HIGH], a + + ret + +play_ch4_note: + ld a, [mute_channels] + retMute 3 + + ;; Play a "note" on channel 4 (noise) + ld a, [temp_note_value] + ld [channel_period4+1], a + ldh [rAUD4POLY], a + + ;; Get the highmask and apply it. + ld a, [highmask4] + ldh [rAUD4GO], a + + ret + + +;;; Performs an effect on a given channel. +;;; Param: E = Channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: B = Effect type (upper 4 bits ignored) +;;; Param: C = Effect parameters (depend on FX type) +;;; Destroy: AF BC DE HL +do_effect: + ;; Strip the instrument bits off leaving only effect code + ld a, b + and %00001111 + ;; Multiply by 2 to get offset into table + add a + + add LOW(.jump) + ld l, a + adc HIGH(.jump) + sub l + ld h, a + + ld a, [hl+] + ld h, [hl] + ld l, a + + ld b, e + ld a, [tick] + or a ; We can return right off the bat if it's tick zero + jp hl + +.jump: + ;; Jump table for effect + dw fx_arpeggio ;0xy + dw fx_porta_up ;1xy + dw fx_porta_down ;2xy + dw fx_toneporta ;3xy + dw fx_vibrato ;4xy + dw fx_set_master_volume ;5xy ; global + dw fx_call_routine ;6xy + dw fx_note_delay ;7xy + dw fx_set_pan ;8xy ; global + dw fx_set_duty ;9xy + dw fx_vol_slide ;Axy + dw fx_pos_jump ;Bxy ; global + dw fx_set_volume ;Cxy + dw fx_pattern_break ;Dxy ; global + dw fx_note_cut ;Exy + dw fx_set_speed ;Fxy ; global + + +;;; Processes (global) effect 5, "set master volume". +;;; Param: C = Value to write to NR50 +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: A +fx_set_master_volume: + ret nz + + ld a, c + ldh [rAUDVOL], a + ret + + +;;; Processes effect 6, "call routine". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = Routine ID +;;; Param: A = Current tick +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: Anything the routine does +fx_call_routine: + sla c + ld a, [routines] + add c + ld l, a + ld a, [routines+1] + adc 0 + ld h, a + + ld a, [hl+] + ld h, [hl] + ld l, a + + ld a, [tick] + or a ; set zero flag if tick 0 for compatibility +IF DEF(GBDK) ; Pass the tick counter as a SDCC call parameter + push af + inc sp + push bc + call .call_hl + add sp, 3 + ret + +.call_hl: +ENDC + jp hl + + +;;; Processes (global) effect 8, "set pan". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = Value to write to NR51 +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: A +fx_set_pan: + ret nz + + ;; Pretty simple. The editor can create the correct value here without a bunch + ;; of bit shifting manually. + ld a, c + ldh [rAUDTERM], a + ret + + +;;; Processes effect 9, "set duty cycle". +;;; Param: B = Current channel ID (0 = CH1, anything else = CH2) +;;; Param: C = Value to write to NRx1 +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: AF +fx_set_duty: + ret nz + + ;; $900 = 12.5% + ;; $940 = 25% + ;; $980 = 50% + ;; $9C0 = 75% + + ld a, b + or a + ld a, [mute_channels] + jr z, .chan1 +.chan2: + retMute 1 + ld a, c + ldh [rAUD2LEN], a + ret +.chan1: + retMute 0 + ld a, c + ldh [rAUD1LEN], a + ret + + +;;; Processes effect A, "volume slide". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = FX param; either nibble should be 0, otherwise weird (unspecified) behavior may arise +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: AF C DE HL +fx_vol_slide: + ret nz + + ;; This is really more of a "retrigger note with lower volume" effect and thus + ;; isn't really that useful. Instrument envelopes should be used instead. + ;; Might replace this effect with something different if a new effect is + ;; ever needed. + + ;; check channel mute + + ;; 0 → $01, 1 → $02, 2 → $04, 3 → $05 + ;; Overall, these two instructions add 1 to the number. + ;; However, the first instruction will generate a carry for inputs of $02 and $03; + ;; the `adc` will pick the carry up, and "separate" 0 / 1 from 2 / 3 by an extra 1. + ;; Luckily, this yields correct results for 0 ($01), 1 ($02), and 2 ($03 + 1 = $04). + ;; We'll see about fixing 3 afterwards. + add -2 + adc 3 + ;; After being shifted left, the inputs are $02, $04, $08 and $0A; all are valid BCD, + ;; except for $0A. Since we just performed `add a`, DAA will correct the latter to $10. + ;; (This should be correctly emulated everywhere, since the inputs are identical to + ;; "regular" BCD.) + ;; When shifting the results back, we'll thus get $01, $02, $04 and $08! + add a + daa + rra + ld d, a + ld a, [mute_channels] + and d + ret nz + + ;; setup the up and down params + ld a, c + and %00001111 + ld d, a + + ld a, c + and %11110000 + ld e, a + swap e + + ; There are 5 bytes between each envelope register + ld a, b + add a + add a + add b + add LOW(rAUD1ENV) + ld c, a + + ldh a, [c] + and %11110000 + swap a + sub d + jr nc, .cont1 + xor a +.cont1: + add e + cp $10 + jr c, .cont2 + ld a, $F +.cont2: + swap a + ldh [c], a + + ; Go to rAUDxGO, which is 2 bytes after + inc c + inc c + ldh a, [c] + or %10000000 + ldh [c], a + + jr play_note + + +;;; Processes effect 7, "note delay". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = Amount of ticks by which to delay the note +;;; Caveats: 0 never plays the note, and a delay longer than a row's duration skips the note entirely +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: AF D HL +fx_note_delay: + jr nz, .play_note + + ;; Just store the note into the channel period, and don't play a note. + ld d, 0 + call ptr_to_channel_member + + ld a, [temp_note_value] + ld [hl+], a + ld a, [temp_note_value+1] + ld [hl], a + + ;; Don't call _playnote. This is done by grabbing the return + ;; address and manually skipping the next call instruction. + ret_dont_call_playnote + +.play_note: + cp c + ret nz ; wait until the correct tick to play the note + + ;; fallthrough + + +;;; Plays a channel's current note. +;;; Param: B = Which channel (0 = CH1, 1 = CH2, etc.) +;;; Destroy: AF D HL +play_note: + ld d, 0 + call ptr_to_channel_member + + ;; TODO: Change this to accept HL instead? + ld a, [hl+] + ld [temp_note_value], a + ld a, [hl] + ld [temp_note_value+1], a + + ld a, b + add a + add LOW(play_note_routines) + ld l, a + adc HIGH(play_note_routines) + sub l + ld h, a + jp hl + + +;;; Processes (global) effect F, "set speed". +;;; Param: C = New amount of ticks per row +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: A +fx_set_speed: + ret nz + + ld a, c + ld [ticks_per_row], a + ret + + +hUGE_set_position:: +;;; Processes (global) effect B, "position jump". +;;; Param: C = ID of the order to jump to +;;; Destroy: A +fx_pos_jump: + ld a, 1 + ld [row_break], a + ld a, c + ld [next_order], a + ret + + +;;; Processes (global) effect D, "pattern break". +;;; Param: C = ID of the next order's row to start on +;;; Destroy: A +fx_pattern_break: + ld a, c + ld [row_break], a + ret + + +;;; Processes effect E, "note cut". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = Tick to cut the note on (TODO: what does cutting on tick 0 do?) +;;; Param: A = Current tick +;;; Destroy: A +fx_note_cut: + cp c + ret nz + + ;; check channel mute + + ;; 0 → $01, 1 → $02, 2 → $04, 3 → $05 + ;; Overall, these two instructions add 1 to the number. + ;; However, the first instruction will generate a carry for inputs of $02 and $03; + ;; the `adc` will pick the carry up, and "separate" 0 / 1 from 2 / 3 by an extra 1. + ;; Luckily, this yields correct results for 0 ($01), 1 ($02), and 2 ($03 + 1 = $04). + ;; We'll see about fixing 3 afterwards. + add -2 + adc 3 + ;; After being shifted left, the inputs are $02, $04, $08 and $0A; all are valid BCD, + ;; except for $0A. Since we just performed `add a`, DAA will correct the latter to $10. + ;; (This should be correctly emulated everywhere, since the inputs are identical to + ;; "regular" BCD.) + ;; When shifting the results back, we'll thus get $01, $02, $04 and $08! + add a + daa + rra + ld d, a + ld a, [mute_channels] + and d + ret nz + + ;; fallthrough + + +;;; Cuts note on a channel. +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Destroy: AF HL +note_cut: + ld a, b + add a + add a + add b ; multiply by 5 + add LOW(rAUD1ENV) + ld l, a + ld h, HIGH(rAUD1ENV) + xor a + ld [hl+], a + ld a, b + cp 2 + ret z ; return early if CH3-- no need to retrigger note + + ;; Retrigger note + inc l ; Not `inc hl` because H stays constant (= $FF) + ld [hl], $FF + ret + + +;;; Processes effect C, "set volume". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = Volume to set the channel to +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: AF BC +fx_set_volume: + ret nz ; Return if we're not on tick zero. + + swap c + ld a, [mute_channels] + dec b + jr z, .set_chn_2_vol + dec b + jr z, .set_chn_3_vol + dec b + jr z, .set_chn_4_vol + +.set_chn_1_vol: + retMute 0 + + ldh a, [rAUD1ENV] + and %00001111 + or c + ldh [rAUD1ENV], a + ret + +.set_chn_2_vol: + retMute 1 + + ldh a, [rAUD2ENV] + and %00001111 + or c + ldh [rAUD2ENV], a + ret + +.set_chn_3_vol: + retMute 2 + + ;; "Quantize" the more finely grained volume control down to one of 4 values. + ld a, c + cp 10 << 4 + jr nc, .one + cp 5 << 4 + jr nc, .two + or a + jr z, .done ; Zero maps to zero +.three: + ld a, %01100000 + jr .done +.two: + ld a, %01000000 + jr .done +.one: + ld a, %00100000 +.done: + ldh [rAUD3LEVEL], a + ret + +.set_chn_4_vol: + retMute 3 + + ld a, c + ldh [rAUD4ENV], a + ret + + +;;; Processes effect 4, "vibrato". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = FX param +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: AF B DE HL +fx_vibrato: + ret z + + ;; Extremely poor man's vibrato. + ;; Speed values: + ;; (0x0 = 1.0) + ;; (0x1 = 0.5) + ;; (0x3 = 0.25) + ;; (0x7 = 0.125) + ;; (0xf = 0.0625) + ld d, 4 + call ptr_to_channel_member + + ld a, c + and %11110000 + swap a + ld e, a + + ld a, [counter] + and e + ld a, [hl] + jr z, .go_up +.restore: + call get_note_period + jr .finish_vibrato +.go_up: + call get_note_period + ld a, c + and %00001111 + add_a_to_hl +.finish_vibrato: + ld d, h + ld e, l + xor a + jp update_channel_freq + + +;;; Processes effect 8, "arpeggio". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = Offsets in semitones (each nibble) +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: AF B DE HL +fx_arpeggio: + ret z + + ld d, 4 + call ptr_to_channel_member + ld d, [hl] + + ld a, [tick] + dec a + + ;; TODO: A crappy modulo, because it's not a multiple of four :( + + jr .test_greater_than_two +.greater_than_two: + sub 3 +.test_greater_than_two: + cp 3 + jr nc, .greater_than_two + + ;; Multiply by 2 to get offset into table + add a + + add LOW(.arp_options) + ld l, a + adc HIGH(.arp_options) + sub l + ld h, a + jp hl + +.arp_options: + jr .set_arp1 + jr .set_arp2 + ;; No `jr .reset_arp` + +.reset_arp: + ld a, d + jr .finish_skip_add + +.set_arp2: + ld a, c + swap a + db $FE ; cp gobbles next byte + +.set_arp1: + ld a, c +.finish_arp: + and %00001111 + add d +.finish_skip_add: + call get_note_period + ld d, h + ld e, l + xor a + jp update_channel_freq + + +;;; Processes effect 1, "portamento up". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = How many units to slide the pitch by per tick +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: A B DE HL +fx_porta_up: + ret z + + ld d, 0 + call ptr_to_channel_member + + ;; Add C to 16-bit value at HL + ld a, [hl+] + add c + ld e, a + adc [hl] + sub e + + ;; Write back +.finish: + ld d, a ; Store A for call to `update_channel_freq` + ld [hl-], a + ld [hl], e + + xor a + jp update_channel_freq + + +;;; Processes (global) effect 2, "portamento down". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = How many units to slide the pitch down by per tick +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: A B DE HL +fx_porta_down: + ret z + + ld d, 0 + call ptr_to_channel_member + + ;; Subtract C from 16-bit value at [HL] + ld a, [hl+] + sub c + ld e, a + sbc a + add [hl] + + ;; Write back + jr fx_porta_up.finish + + +;;; Processes effect 2, "tone portamento". +;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.) +;;; Param: C = Target note +;;; Param: ZF = Set if and only if on tick 0 +;;; Destroy: A B DE HL +fx_toneporta: + jr nz, .do_toneporta + + ;; We're on tick zero, so just move the temp note value into the toneporta target. + ld d, 2 + call ptr_to_channel_member + + ;; If the note is nonexistent, then just return + ld a, [temp_note_value] + or a + jr z, .return_skip + + ld [hl+], a + ld a, [temp_note_value+1] + ld [hl], a + + ;; Don't call _playnote. This is done by grabbing the return + ;; address and manually skipping the next call instruction. +.return_skip: + ret_dont_call_playnote + +.do_toneporta: + ld d, 0 + call ptr_to_channel_member + push hl + + ;; Read current period + ld a, [hl+] + ld e, a + ld a, [hl+] + ld d, a + + ;; Read target period + ld a, [hl+] + ld h, [hl] + ld l, a + + ;; Do we need to porta up, or down? Compute (current - target) and check carry to know + sub e + ld a, h + sbc d + jr c, .porta_down ; Current period (DE) is higher than target one (HL), so down we go! + + ;; Add offset to current freq + ld a, e + add c + ld e, a + adc d + sub e + ld d, a + ;; We don't need to worry about overflow given the relatively low values we work with + + ld c, 0 ; The overshoot comparison should yield no carry, like the above one + jr .check_overshoot + +.porta_down: + ;; Subtract offset from current freq + ld a, e + sub c + ld e, a + sbc a + add d + ld d, a + jr c, .overshot ; There will be no underflows under my watch! + + ld c, $FF ; The overshoot comparison should yield carry, like the above one + +.check_overshoot: + ld a, l + sub e + ld a, h + sbc d + rra ; Shift carry into bit 7 + xor c ; XOR it with provided value + rla ; Shift maybe-toggled carry back + jr nc, .no_overshoot +.overshot: + ;; Override computed new period with target + ld d, h + ld e, l +.no_overshoot: + + pop hl + ld a, e + ld [hl+], a + ld [hl], d + + ;; Do not retrigger channel + ld a, 6 + add_a_to_hl + ld a, [hl] + res 7, [hl] + ;; B must be preserved for this + jp update_channel_freq + + +;; TODO: Find some way to de-duplicate this code! +;;; Computes the pointer to a CH4 instrument. +;;; Param: B = The instrument's ID +;;; Param: DE = Instrument pointer table +;;; Return: DE = Pointer to the instrument +;;; Return: ZF = Set if and only if there was no instrument (ID == 0) +;;; Destroy: AF +setup_instrument_pointer_ch4: + ;; Call with: + ;; Instrument/High nibble of effect in B + ;; Stores whether the instrument was real in the Z flag + ;; Stores the instrument pointer in DE + ld a, b + and %11110000 + swap a + ret z ; If there's no instrument, then return early. + + dec a ; Instrument 0 is "no instrument" + add a + jr setup_instrument_pointer.finish + +;;; Computes the pointer to an instrument. +;;; Param: B = The instrument's ID +;;; Param: DE = Instrument pointer table +;;; Return: DE = Pointer to the instrument +;;; Return: ZF = Set if and only if there was no instrument (ID == 0) +;;; Destroy: AF +setup_instrument_pointer: + ;; Call with: + ;; Instrument/High nibble of effect in B + ;; Stores whether the instrument was real in the Z flag + ;; Stores the instrument pointer in DE + ld a, b + and %11110000 + swap a + ret z ; If there's no instrument, then return early. + + dec a ; Instrument 0 is "no instrument" +.finish: + ;; Shift left twice to multiply by 4 + add a + add a + + add_a_to_de + + rla ; reset the Z flag + ret + + +_hUGE_dosound_banked:: +_hUGE_dosound:: +;;; Ticks the sound engine once. +;;; Destroy: AF BC DE HL +hUGE_dosound:: + ld a, [tick] + or a + jp nz, process_effects + + ;; Note playback + ld hl, pattern1 + ld de, channel_note1 + call get_current_note + push af + jr nc, .do_setvol1 + + load_de_ind duty_instruments + call setup_instrument_pointer + ld a, [highmask1] + res 7, a ; Turn off the "initial" flag + jr z, .write_mask1 + + checkMute 0, .do_setvol1 + + ld a, [de] + inc de + ldh [rAUD1SWEEP], a + ld a, [de] + inc de + ldh [rAUD1LEN], a + ld a, [de] + ldh [rAUD1ENV], a + inc de + ld a, [de] + +.write_mask1: + ld [highmask1], a + +.do_setvol1: + ld a, l + ld [temp_note_value], a + ld a, h + ld [temp_note_value+1], a + + ld e, 0 + call do_effect + + pop af + call c, play_ch1_note + +process_ch2: + ;; Note playback + ld hl, pattern2 + ld de, channel_note2 + call get_current_note + push af + jr nc, .do_setvol2 + + load_de_ind duty_instruments + call setup_instrument_pointer + ld a, [highmask2] + res 7, a ; Turn off the "initial" flag + jr z, .write_mask2 + + checkMute 1, .do_setvol2 + + inc de + ld a, [de] + inc de + ldh [rAUD2LEN], a + ld a, [de] + ldh [rAUD2ENV], a + inc de + ld a, [de] + +.write_mask2: + ld [highmask2], a + +.do_setvol2: + ld a, l + ld [temp_note_value], a + ld a, h + ld [temp_note_value+1], a + + ld e, 1 + call do_effect + + pop af + call c, play_ch2_note + +process_ch3: + ld hl, pattern3 + ld de, channel_note3 + call get_current_note + + ld a, l + ld [temp_note_value], a + ld a, h + ld [temp_note_value+1], a + + push af + + jr nc, .do_setvol3 + + load_de_ind wave_instruments + call setup_instrument_pointer + ld a, [highmask3] + res 7, a ; Turn off the "initial" flag + jr z, .write_mask3 + + checkMute 2, .do_setvol3 + + ld a, [de] + inc de + ldh [rAUD3LEN], a + ld a, [de] + inc de + ldh [rAUD3LEVEL], a + ld a, [de] + inc de + + ;; Check to see if we need to copy a wave and then do so + ld hl, current_wave + cp [hl] + jr z, .no_wave_copy + ld [hl], a + swap a + add_a_ind_ret_hl waves + + xor a + ldh [rAUD3ENA], a + +FOR OFS, 16 + ld a, [hl+] + ldh [_AUD3WAVERAM + OFS], a +ENDR + + ld a, %10000000 + ldh [rAUD3ENA], a + +.no_wave_copy: + ld a, [de] + +.write_mask3: + ld [highmask3], a + +.do_setvol3: + ld e, 2 + call do_effect + + pop af + call c, play_ch3_note + +process_ch4: + ld hl, pattern4 + ld a, [hl+] + ld c, a + ld b, [hl] + call get_current_row + ld [channel_note4], a + cp LAST_NOTE + push af + jr nc, .do_setvol4 + + call get_note_poly + ld [temp_note_value], a + + ld de, 0 + call setup_instrument_pointer + + ld a, [highmask4] + res 7, a ; Turn off the "initial" flag + jr z, .write_mask4 + + checkMute 3, .do_setvol4 + + load_hl_ind noise_instruments + sla e + add hl, de + + ld a, [hl+] + ldh [rAUD4ENV], a + + ld a, [hl] + and %00111111 + ldh [rAUD4LEN], a + + ld a, [temp_note_value] + ld d, a + ld a, [hl] + and %10000000 + swap a + or d + ld [temp_note_value], a + + ld a, [hl] + and %01000000 + or %10000000 +.write_mask4: + ld [highmask4], a + +.do_setvol4: + ld e, 3 + call do_effect + + pop af + call c, play_ch4_note + + ;; finally just update the tick/order/row values + jp process_tick + +process_effects: + ;; Only do effects if not on tick zero + checkMute 0, .after_effect1 + + ld hl, pattern1 + ld a, [hl+] + ld c, a + ld b, [hl] + call get_current_row + + ld a, c + or a + jr z, .after_effect1 + + ld e, 0 + call do_effect ; make sure we never return with ret_dont_call_playnote macro + +.after_effect1: + checkMute 1, .after_effect2 + + ld hl, pattern2 + ld a, [hl+] + ld c, a + ld b, [hl] + call get_current_row + + ld a, c + or a + jr z, .after_effect2 + + ld e, 1 + call do_effect ; make sure we never return with ret_dont_call_playnote macro + +.after_effect2: + checkMute 2, .after_effect3 + + ld hl, pattern3 + ld a, [hl+] + ld c, a + ld b, [hl] + call get_current_row + + ld a, c + or a + jr z, .after_effect3 + + ld e, 2 + call do_effect ; make sure we never return with ret_dont_call_playnote macro + +.after_effect3: + checkMute 3, .after_effect4 + + ld hl, pattern4 + ld a, [hl+] + ld c, a + ld b, [hl] + call get_current_row + cp LAST_NOTE + jr nc, .done_macro + ld h, a + + load_de_ind noise_instruments + call setup_instrument_pointer_ch4 + jr z, .done_macro ; No instrument, thus no macro + + ld a, [tick] + cp 7 + jr nc, .done_macro + + inc de + + ld l, a + ld a, h + ld h, 0 + add hl, de + add [hl] + call get_note_poly + ld l, a + ld a, [de] + ld e, a + and %10000000 + swap a + or l + ldh [rAUD4POLY], a + + ld a, e + and %01000000 + ldh [rAUD4GO], a + +.done_macro: + ld a, c + or a + jr z, .after_effect4 + + ld e, 3 + call do_effect ; make sure we never return with ret_dont_call_playnote macro + +.after_effect4: + +process_tick: + ld hl, counter + inc [hl] + + ld a, [ticks_per_row] + ld b, a + + ld hl, tick + ld a, [hl] + inc a + + cp b + jr z, .newrow + + ld [hl], a + ret + +.newrow: + ;; Reset tick to 0 + ld [hl], 0 + + ;; Check if we need to perform a row break or pattern break + ld a, [row_break] + or a + jr z, .no_break + + ;; These are offset by one so we can check to see if they've + ;; been modified + dec a + ld b, a + + ld hl, row_break + xor a + ld [hl-], a + or [hl] ; a = [next_order], zf = ([next_order] == 0) + ld [hl], 0 + + jr z, .neworder + + dec a + add a ; multiply order by 2 (they are words) + + jr .update_current_order + +.no_break: + ;; Increment row. + ld a, [row] + inc a + ld b, a + cp PATTERN_LENGTH + jr nz, .noreset + + ld b, 0 +.neworder: + ;; Increment order and change loaded patterns + ld a, [order_cnt] + ld c, a + ld a, [current_order] + add 2 + cp c + jr nz, .update_current_order + xor a +.update_current_order: + ;; Call with: + ;; A: The order to load + ;; B: The row for the order to start on + ld [current_order], a + ld c, a + call load_patterns + +.noreset: + ld a, b + ld [row], a + +IF DEF(PREVIEW_MODE) + db $fd ; signal row update to tracker +ENDC + ret + +note_table: +include "huge_note_table.inc" + + +IF DEF(GBDK) + +SECTION "hUGEDriver GBDK wrappers", ROM0 + +_hUGE_init_banked:: + ld hl, sp+2+4 + jr continue_init +_hUGE_init:: + ld hl, sp+2 +continue_init: + push bc + ld a, [hl+] + ld h, [hl] + ld l, a + call hUGE_init + pop bc + ret + +_hUGE_mute_channel_banked:: + ld hl, sp+3+4 + jr continue_mute +_hUGE_mute_channel:: + ld hl, sp+3 +continue_mute: + push bc + ld a, [hl-] + and 1 + ld c, a + ld b, [hl] + call hUGE_mute_channel + pop bc + ret + +_hUGE_set_position_banked:: + ld hl, sp+2+4 + jr continue_set_position +_hUGE_set_position:: + ld hl, sp+2 +continue_set_position: + push bc + ld c, [hl] + call hUGE_set_position + pop bc + ret + +ENDC diff --git a/src/hugedriver.h b/src/hugedriver.h new file mode 100644 index 0000000..f4e6886 --- /dev/null +++ b/src/hugedriver.h @@ -0,0 +1,119 @@ +#ifndef HUGEDRIVER_H_INCLUDE +#define HUGEDRIVER_H_INCLUDE + +#define DN(A, B, C) (unsigned char)(A),(unsigned char)((B << 4) | (C >> 8)),(unsigned char)(C & 0xFF) + +#define C_3 0 +#define Cs3 1 +#define D_3 2 +#define Ds3 3 +#define E_3 4 +#define F_3 5 +#define Fs3 6 +#define G_3 7 +#define Gs3 8 +#define A_3 9 +#define As3 10 +#define B_3 11 +#define C_4 12 +#define Cs4 13 +#define D_4 14 +#define Ds4 15 +#define E_4 16 +#define F_4 17 +#define Fs4 18 +#define G_4 19 +#define Gs4 20 +#define A_4 21 +#define As4 22 +#define B_4 23 +#define C_5 24 +#define Cs5 25 +#define D_5 26 +#define Ds5 27 +#define E_5 28 +#define F_5 29 +#define Fs5 30 +#define G_5 31 +#define Gs5 32 +#define A_5 33 +#define As5 34 +#define B_5 35 +#define C_6 36 +#define Cs6 37 +#define D_6 38 +#define Ds6 39 +#define E_6 40 +#define F_6 41 +#define Fs6 42 +#define G_6 43 +#define Gs6 44 +#define A_6 45 +#define As6 46 +#define B_6 47 +#define C_7 48 +#define Cs7 49 +#define D_7 50 +#define Ds7 51 +#define E_7 52 +#define F_7 53 +#define Fs7 54 +#define G_7 55 +#define Gs7 56 +#define A_7 57 +#define As7 58 +#define B_7 59 +#define C_8 60 +#define Cs8 61 +#define D_8 62 +#define Ds8 63 +#define E_8 64 +#define F_8 65 +#define Fs8 66 +#define G_8 67 +#define Gs8 68 +#define A_8 69 +#define As8 70 +#define B_8 71 +#define LAST_NOTE 72 +#define ___ 90 + +typedef void (*hUGERoutine_t)(unsigned char param, unsigned char ch, unsigned char tick); + +typedef struct hUGESong_t { + unsigned char tempo; + const unsigned char * order_cnt; + const unsigned char ** order1, ** order2, ** order3, ** order4; + const unsigned char * duty_instruments, * wave_instruments, * noise_instruments; + const hUGERoutine_t ** routines; + const unsigned char * waves; +} hUGESong_t; + +// initialize the driver with song data +void hUGE_init(const hUGESong_t * song); +void hUGE_init_banked(const hUGESong_t * song) __banked; + +// driver routine +void hUGE_dosound(); +void hUGE_dosound_banked() __banked; + +enum hUGE_channel_t {HT_CH1 = 0, HT_CH2, HT_CH3, HT_CH4}; +enum hUGE_mute_t {HT_CH_PLAY = 0, HT_CH_MUTE}; + +void hUGE_mute_channel(enum hUGE_channel_t ch, enum hUGE_mute_t mute); +void hUGE_mute_channel_banked(enum hUGE_channel_t ch, enum hUGE_mute_t mute) __banked; + +void hUGE_set_position(unsigned char pattern); +void hUGE_set_position_banked(unsigned char pattern) __banked; + +extern volatile unsigned char hUGE_current_wave; +#define hUGE_NO_WAVE 100 + +inline void hUGE_reset_wave() { + hUGE_current_wave = hUGE_NO_WAVE; +} +inline void hUGE_reset_wave_banked() { + hUGE_current_wave = hUGE_NO_WAVE; +} + +#endif diff --git a/src/main.c b/src/main.c index 29c789f..b1cbdec 100644 --- a/src/main.c +++ b/src/main.c @@ -14,6 +14,7 @@ void main() { oam_init(); vram_init(); + audio_init(); // Set up background & sprites rBGP = 0b11100100; @@ -25,6 +26,12 @@ void main() { // handling. We only do this so HALT waits for VBLANK. rIF = 0; rIE = IE_VBLANK; + // rIE = IE_VBLANK | IE_LCDC; + + // rSTAT |= STAT_LYCF; + // rLYC = 0; + + ENABLE_INTERRUPTS(); while (1) { switch (game_state) { diff --git a/src/map.c b/src/map.c index 00bc23a..5a3a9a7 100644 --- a/src/map.c +++ b/src/map.c @@ -32,8 +32,8 @@ void map_load(map_t *map) { rSCY = INIT_SCY; PLAYER.x = (MAP.spawn_x - MAP.camera_x) * 8; - uint8_t y = (MAP.spawn_y - MAP.camera_y) * 8; - PLAYER.y = y << 8; + PLAYER.y_hi = (MAP.spawn_y - MAP.camera_y) * 8; + PLAYER.y_lo = 0; memcpy(VRAM_TILE_PTR(TILE_INDEX_BACKGROUND), (uint8_t *)MAP.tile_ptr, MAP.tile_size); diff --git a/src/player.c b/src/player.c index d840c51..c3a56f2 100644 --- a/src/player.c +++ b/src/player.c @@ -13,30 +13,48 @@ #define PLAYER_INIT_FALL_VY 0xff1a //!< -1.1015625 #define MAX_VY 0xf900 //!< -7.0 +#define SWITCH_ANIM_STATE(state) \ + do { \ + PLAYER_ACTOR->anim = (state); \ + PLAYER_ACTOR->frame_counter = 0; \ + PLAYER_ACTOR->frame_idx = 0; \ + } while (0) + player_t PLAYER; ASSET(player_tiles, "player.2bpp"); ASSET(player_map, "player.map"); void player_init(void) { + PLAYER.state = PLAYER_STATE_STAND; memcpy(VRAM_TILE_PTR(TILE_INDEX_PLAYER), &player_tiles[0], player_tiles_end - player_tiles); } void player_update(void) { if ((joypad_pressed & PAD_UP) && PLAYER.state != PLAYER_STATE_JUMP) { + SWITCH_ANIM_STATE(ANIM_JUMP); PLAYER.state = PLAYER_STATE_JUMP; PLAYER.vy = PLAYER_INIT_JUMP_VY; } if (PLAYER.state == PLAYER_STATE_JUMP) { PLAYER.vy -= GRAVITY; - PLAYER.y -= PLAYER.vy; + *((uint16_t*)&PLAYER.y_lo) -= PLAYER.vy; if (player_bg_collides()) { - PLAYER.y += PLAYER.vy; if (PLAYER.vy & (1 << 15)) { - PLAYER.state = PLAYER_STATE_WALK; + PLAYER.y_hi--; + while (player_bg_collides()) { + PLAYER.y_hi--; + } + SWITCH_ANIM_STATE(ANIM_STAND); + PLAYER.state = PLAYER_STATE_STAND; + } else { + PLAYER.y_hi++; + while (player_bg_collides()) { + PLAYER.y_hi++; + } } PLAYER.vy = 0; } @@ -62,5 +80,5 @@ void player_update(void) { } PLAYER_ACTOR->x = PLAYER.x; - PLAYER_ACTOR->y = PLAYER.y >> 8; + PLAYER_ACTOR->y = PLAYER.y_hi; } diff --git a/src/player.h b/src/player.h index 9aef0ab..eac2fb1 100644 --- a/src/player.h +++ b/src/player.h @@ -13,7 +13,8 @@ typedef enum { typedef struct { uint8_t x; - uint16_t y; + uint8_t y_lo; + uint8_t y_hi; uint16_t vy; uint8_t dir; uint8_t state; diff --git a/src/songs/title.asm b/src/songs/title.asm new file mode 100644 index 0000000..53be8a8 --- /dev/null +++ b/src/songs/title.asm @@ -0,0 +1,634 @@ +include "huge.inc" + +SECTION "Song Data", ROMX + +_song_title:: +song_title:: +db 7 +dw order_cnt +dw order1, order2, order3, order4 +dw duty_instruments, wave_instruments, noise_instruments +dw routines +dw waves + +order_cnt: db 14 +order1: dw P0,P4,P0,P4,P0,P0,P0 +order2: dw P1,P2,P1,P2,P5,P5,P1 +order3: dw P10,P10,P10,P10,P10,P10,P10 +order4: dw P10,P3,P3,P3,P3,P3,P10 + +P0: + dn D_3,1,$000 + dn ___,0,$000 + dn D_3,1,$C00 + dn ___,0,$000 + dn D_4,1,$000 + dn ___,0,$000 + dn D_4,1,$C00 + dn ___,0,$000 + dn D_5,1,$000 + dn ___,0,$000 + dn D_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D#3,1,$000 + dn ___,0,$000 + dn D#3,1,$C00 + dn ___,0,$000 + dn D#4,1,$000 + dn ___,0,$000 + dn D#4,1,$C00 + dn ___,0,$000 + dn D#5,1,$000 + dn ___,0,$000 + dn D#5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D_3,1,$000 + dn ___,0,$000 + dn D_3,1,$C00 + dn ___,0,$000 + dn D_4,1,$000 + dn ___,0,$000 + dn D_4,1,$C00 + dn ___,0,$000 + dn D_5,1,$000 + dn ___,0,$000 + dn D_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D#3,1,$000 + dn ___,0,$000 + dn D#3,1,$C00 + dn ___,0,$000 + dn D#4,1,$000 + dn ___,0,$000 + dn D#4,1,$C00 + dn ___,0,$000 + dn D#5,1,$000 + dn ___,0,$000 + dn D#5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + +P1: + dn C_4,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn F_4,1,$000 + dn ___,0,$000 + dn F_4,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D_5,1,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn ___,0,$000 + dn F_4,1,$000 + dn ___,0,$000 + dn F_4,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn F_4,1,$000 + dn ___,0,$000 + dn F_4,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn A#3,1,$000 + dn ___,0,$000 + dn C_4,1,$000 + dn ___,0,$000 + +P2: + dn D_4,1,$000 + dn ___,0,$000 + dn D_4,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D_4,1,$000 + dn ___,0,$000 + dn D_4,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_4,1,$000 + dn ___,0,$000 + dn C_4,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_3,1,$000 + dn ___,0,$000 + dn D_3,1,$000 + dn ___,0,$000 + dn D_3,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + +P3: + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn C_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn C_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn C_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn C_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn C_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn C_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn C_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn C_5,1,$000 + dn C_5,1,$C00 + +P4: + dn D_3,1,$000 + dn ___,0,$000 + dn D_3,1,$C00 + dn ___,0,$000 + dn D_4,1,$000 + dn ___,0,$000 + dn D_4,1,$C00 + dn ___,0,$000 + dn D_5,1,$000 + dn ___,0,$000 + dn D_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D#3,1,$000 + dn ___,0,$000 + dn D#3,1,$C00 + dn ___,0,$000 + dn D#4,1,$000 + dn ___,0,$000 + dn D#4,1,$C00 + dn ___,0,$000 + dn D#5,1,$000 + dn ___,0,$000 + dn D#5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D_3,1,$000 + dn ___,0,$000 + dn D_3,1,$C00 + dn ___,0,$000 + dn D_4,1,$000 + dn ___,0,$000 + dn D_4,1,$C00 + dn ___,0,$000 + dn D_5,1,$000 + dn ___,0,$000 + dn D_5,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D#3,1,$000 + dn ___,0,$000 + dn D#3,1,$C00 + dn ___,0,$000 + dn D#4,1,$000 + dn ___,0,$000 + dn D#4,1,$C00 + dn ___,0,$000 + dn D#4,1,$000 + dn ___,0,$000 + dn D_4,1,$000 + dn ___,0,$000 + dn C_4,1,$000 + dn ___,0,$000 + dn C_4,1,$C00 + dn ___,0,$000 + +P5: + dn G_6,1,$000 + dn ___,0,$000 + dn G#6,1,$000 + dn ___,0,$000 + dn A#6,1,$000 + dn ___,0,$000 + dn G#6,1,$000 + dn ___,0,$000 + dn G#6,1,$000 + dn ___,0,$000 + dn A#6,1,$000 + dn ___,0,$000 + dn G#6,1,$000 + dn ___,0,$000 + dn G_6,1,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn G_6,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn G_6,1,$000 + dn ___,0,$000 + dn G#6,1,$000 + dn ___,0,$000 + dn A#6,1,$000 + dn ___,0,$000 + dn G#6,1,$000 + dn ___,0,$000 + dn G#6,1,$000 + dn ___,0,$000 + dn A#6,1,$000 + dn ___,0,$000 + dn G#6,1,$000 + dn ___,0,$000 + dn G_6,1,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D#6,1,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn D#6,1,$C00 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + +P10: + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + dn ___,0,$000 + +duty_instruments: +itSquareinst1: db 8,0,240,128 +itSquareinst2: db 8,64,240,128 +itSquareinst3: db 8,128,240,128 +itSquareinst4: db 8,192,240,128 +itSquareinst5: db 8,0,241,128 +itSquareinst6: db 8,64,241,128 +itSquareinst7: db 8,128,241,128 +itSquareinst8: db 8,192,241,128 +itSquareinst9: db 8,128,240,128 +itSquareinst10: db 8,128,240,128 +itSquareinst11: db 8,128,240,128 +itSquareinst12: db 8,128,240,128 +itSquareinst13: db 8,128,240,128 +itSquareinst14: db 8,128,240,128 +itSquareinst15: db 8,128,240,128 + + +wave_instruments: +itWaveinst1: db 0,32,0,128 +itWaveinst2: db 0,32,1,128 +itWaveinst3: db 0,32,2,128 +itWaveinst4: db 0,32,3,128 +itWaveinst5: db 0,32,4,128 +itWaveinst6: db 0,32,5,128 +itWaveinst7: db 0,32,6,128 +itWaveinst8: db 0,32,7,128 +itWaveinst9: db 0,32,8,128 +itWaveinst10: db 0,32,9,128 +itWaveinst11: db 0,32,10,128 +itWaveinst12: db 0,32,11,128 +itWaveinst13: db 0,32,12,128 +itWaveinst14: db 0,32,13,128 +itWaveinst15: db 0,32,14,128 + + +noise_instruments: +itNoiseinst1: db 240,0,-31,32,-31,32,-31,32 +itNoiseinst2: db 240,0,0,0,0,0,0,0 +itNoiseinst3: db 240,0,0,0,0,0,0,0 +itNoiseinst4: db 240,0,0,0,0,0,0,0 +itNoiseinst5: db 240,0,0,0,0,0,0,0 +itNoiseinst6: db 240,0,0,0,0,0,0,0 +itNoiseinst7: db 240,0,0,0,0,0,0,0 +itNoiseinst8: db 240,0,0,0,0,0,0,0 +itNoiseinst9: db 240,0,0,0,0,0,0,0 +itNoiseinst10: db 240,0,0,0,0,0,0,0 +itNoiseinst11: db 240,0,0,0,0,0,0,0 +itNoiseinst12: db 240,0,0,0,0,0,0,0 +itNoiseinst13: db 240,0,0,0,0,0,0,0 +itNoiseinst14: db 240,0,0,0,0,0,0,0 +itNoiseinst15: db 240,0,0,0,0,0,0,0 + + +routines: +__hUGE_Routine_0: + +__end_hUGE_Routine_0: +ret + +__hUGE_Routine_1: + +__end_hUGE_Routine_1: +ret + +__hUGE_Routine_2: + +__end_hUGE_Routine_2: +ret + +__hUGE_Routine_3: + +__end_hUGE_Routine_3: +ret + +__hUGE_Routine_4: + +__end_hUGE_Routine_4: +ret + +__hUGE_Routine_5: + +__end_hUGE_Routine_5: +ret + +__hUGE_Routine_6: + +__end_hUGE_Routine_6: +ret + +__hUGE_Routine_7: + +__end_hUGE_Routine_7: +ret + +__hUGE_Routine_8: + +__end_hUGE_Routine_8: +ret + +__hUGE_Routine_9: + +__end_hUGE_Routine_9: +ret + +__hUGE_Routine_10: + +__end_hUGE_Routine_10: +ret + +__hUGE_Routine_11: + +__end_hUGE_Routine_11: +ret + +__hUGE_Routine_12: + +__end_hUGE_Routine_12: +ret + +__hUGE_Routine_13: + +__end_hUGE_Routine_13: +ret + +__hUGE_Routine_14: + +__end_hUGE_Routine_14: +ret + +__hUGE_Routine_15: + +__end_hUGE_Routine_15: +ret + +waves: +wave0: db 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255 +wave1: db 0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255 +wave2: db 0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255 +wave3: db 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255 +wave4: db 0,1,18,35,52,69,86,103,120,137,154,171,188,205,222,239 +wave5: db 254,220,186,152,118,84,50,16,18,52,86,120,154,188,222,255 +wave6: db 122,205,219,117,33,19,104,189,220,151,65,1,71,156,221,184 +wave7: db 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +wave8: db 254,252,250,248,246,244,242,240,242,244,246,248,250,252,254,255 +wave9: db 254,221,204,187,170,153,136,119,138,189,241,36,87,138,189,238 +wave10: db 132,17,97,237,87,71,90,173,206,163,23,121,221,32,3,71 +wave11: db 9,221,121,137,227,27,203,59,32,190,49,4,32,179,160,158 +wave12: db 133,46,98,136,183,66,205,189,100,23,57,24,54,125,115,25 +wave13: db 199,94,4,173,222,87,147,208,0,54,220,214,5,148,23,131 +wave14: db 153,183,37,42,197,106,210,23,133,41,236,167,30,208,14,204 +wave15: db 58,64,136,231,133,42,212,220,193,158,147,235,125,194,86,57 + diff --git a/src/title.c b/src/title.c index 45b15db..25f8a8c 100644 --- a/src/title.c +++ b/src/title.c @@ -1,6 +1,7 @@ #include #include "game.h" +#include "hugedriver.h" #include "sdk/assets.h" #include "sdk/hardware.h" #include "sdk/joypad.h" @@ -37,5 +38,9 @@ void title(void) { // Wait for VBLANK. Clear IF since global interrupts are disabled. HALT(); rIF = 0; + + hUGE_dosound(); } + + audio_reset(); }