From 3ec91ad217fb77ae1980b0b935e76307edf63ae1 Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Thu, 29 Apr 2021 16:06:34 +0200 Subject: Initial tests with playing DMG sounds --- src/common.h | 189 +++++++++++++++++++++++++++++++++++++++++++++++------------ src/main.c | 87 ++++++++++++++++++++++++--- src/text.h | 2 +- 3 files changed, 234 insertions(+), 44 deletions(-) diff --git a/src/common.h b/src/common.h index ff5ea12..7a0c50c 100644 --- a/src/common.h +++ b/src/common.h @@ -83,29 +83,45 @@ #define SCREEN_WIDTH 240 #define SCREEN_HEIGHT 160 +// +// Colors. +// + // The GBA in mode 3 expects rbg15 colors in the VRAM, where each component // (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). typedef u16 Color; -// A palette is composed of 16 colors, with color at index 0 being transparent. +// A palette is composed of 16 colors, with color at index 0 being transparent +// for sprites. typedef Color Palette[16]; +// Inline function to calculate the 15 bit color value. +static inline Color +rgb15(u32 red, u32 green, u32 blue ) { + return (blue << 10) | (green << 5) | red; +} + +// Some nice default colors. +#define COLOR_RED rgb15(31, 0, 12) +#define COLOR_BLUE rgb15(2, 15, 30) +#define COLOR_CYAN rgb15(0, 30, 30) +#define COLOR_GREY rgb15(4, 4, 4) +#define COLOR_BLACK rgb15(0, 0, 0) +#define COLOR_WHITE rgb15(28, 28, 28) + // // Tile memory access. // // NOTE: Only defining 4bpp tiles for now. typedef struct Tile { - u32 data[8]; + u32 row[8]; } Tile; +// Screenblocks and charblocks (tile blocks). typedef Tile TileBlock[512]; #define TILE_MEM ((TileBlock*) MEM_VRAM) - -// Screenblocks and charblocks for backgrounds. typedef u16 ScreenBlock[1024]; -typedef Tile CharBlock[512]; -#define CHARBLOCK_MEM ((CharBlock*)MEM_VRAM) #define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) // Screenblock entry bits. @@ -113,40 +129,24 @@ typedef Tile CharBlock[512]; #define SCREENBLOCK_ENTRY_V_FLIP (1 << 0xB) #define SCREENBLOCK_ENTRY_PAL(N) ((N) << 0xC) -size_t se_index(size_t tile_x, size_t tile_y, size_t map_width) { +size_t +se_index(size_t tile_x, size_t tile_y, size_t map_width) { size_t sbb = ((tile_x >> 5) + (tile_y >> 5) * (map_width >> 5)); return sbb * 1024 + ((tile_x & 31) + (tile_y & 31) * 32); } - // We can treat the screen as a HxW matrix. With the following macro we can // write a pixel to the screen at the (x, y) position using: // // FRAMEBUFFER[y][x] = color; // typedef Color Scanline[SCREEN_WIDTH]; -#define FRAMEBUFFER ((Scanline*)MEM_VRAM) +#define FRAMEBUFFER ((Scanline*) MEM_VRAM) #define SCREEN_BUFFER ((u16*) MEM_VRAM) #define PAL_BUFFER_BG ((u16*) MEM_PAL) -#define PAL_BUFFER_SPRITES ((u16*) 0x05000200) +#define PAL_BUFFER_SPRITES ((u16*)(MEM_PAL + 0x200)) #define PAL_BANK_BG ((Palette*) MEM_PAL) -#define PAL_BANK_SPRITES ((Palette*) 0x05000200) - -// -// Colors. -// - -static inline Color -rgb15(u32 red, u32 green, u32 blue ) { - return (blue << 10) | (green << 5) | red; -} - -#define COLOR_RED rgb15(31, 0, 12) -#define COLOR_BLUE rgb15(2, 15, 30) -#define COLOR_CYAN rgb15(0, 30, 30) -#define COLOR_GREY rgb15(4, 4, 4) -#define COLOR_BLACK rgb15(0, 0, 0) -#define COLOR_WHITE rgb15(28, 28, 28) +#define PAL_BANK_SPRITES ((Palette*)(MEM_PAL + 0x200)) // // Sprites. @@ -191,12 +191,6 @@ rgb15(u32 red, u32 green, u32 blue ) { #define OBJ_PRIORITY(N) ((N) << 0xA) #define OBJ_PAL_BANK(N) ((N) << 0xC) -static inline void -wait_vsync(void) { - while(DISP_VCOUNT >= 160); - while(DISP_VCOUNT < 160); -} - // // Mode 4 page flipping // @@ -505,11 +499,12 @@ irs_set(IrqIndex idx, IrsFunc func) { IRQ_CTRL = irq_ctrl; } -#define IRS_MAIN *(IrsFunc*)(0x03007FFC) - // External irs_main function, has to be written in ARM assembly. void irs_main(void); +// Initialize the function pointer for the main IRS routine written in ARM +// assembly and enable interrupts. +#define IRS_MAIN *(IrsFunc*)(0x03007FFC) void irq_init() { IRS_MAIN = irs_main; @@ -519,6 +514,128 @@ irq_init() { // Stub function pointer needed for when we want to enable interrupts that don't // require a custom function, such as for the BIOS VSync. void -irq_stub() {} +irs_stub() {} + +// +// BIOS function declarations. +// + +// These functions declarations can be used to call the BIOS functions from the +// asm code. +int bios_vblank_wait(); +int bios_div(int num, int denom); + +// +// Sound. +// + +// Sound registers. +#define SOUND_SQUARE1_SWEEP *((vu16*)(MEM_IO + 0x60)) +#define SOUND_SQUARE1_CTRL *((vu16*)(MEM_IO + 0x62)) +#define SOUND_SQUARE1_FREQ *((vu16*)(MEM_IO + 0x64)) +#define SOUND_SQUARE2_CTRL *((vu16*)(MEM_IO + 0x68)) +#define SOUND_SQUARE2_FREQ *((vu16*)(MEM_IO + 0x6C)) +#define SOUND_WAVE_MODE *((vu16*)(MEM_IO + 0x70)) +#define SOUND_WAVE_CTRL *((vu16*)(MEM_IO + 0x72)) +#define SOUND_WAVE_FREQ *((vu16*)(MEM_IO + 0x74)) +#define SOUND_NOISE_CTRL *((vu16*)(MEM_IO + 0x78)) +#define SOUND_NOISE_FREQ *((vu16*)(MEM_IO + 0x7C)) +#define SOUND_DMG_MASTER *((vu16*)(MEM_IO + 0x80)) +#define SOUND_DSOUND_MASTER *((vu16*)(MEM_IO + 0x82)) +#define SOUND_STATUS *((vu16*)(MEM_IO + 0x84)) +#define SOUND_BIAS *((vu16*)(MEM_IO + 0x88)) + +// Sound DMG master bits. +#define SOUND_VOLUME_LEFT(N) (N) +#define SOUND_VOLUME_RIGHT(N) ((N) << 4) +#define SOUND_ENABLE_SQUARE1_LEFT (1 << 0x8) +#define SOUND_ENABLE_SQUARE2_LEFT (1 << 0x9) +#define SOUND_ENABLE_WAVE_LEFT (1 << 0xA) +#define SOUND_ENABLE_NOISE_LEFT (1 << 0xB) +#define SOUND_ENABLE_SQUARE1_RIGHT (1 << 0xC) +#define SOUND_ENABLE_SQUARE2_RIGHT (1 << 0xD) +#define SOUND_ENABLE_WAVE_RIGHT (1 << 0xE) +#define SOUND_ENABLE_NOISE_RIGHT (1 << 0xF) + +typedef enum { + SOUND_SQUARE1 = (0x1 << 0), + SOUND_SQUARE2 = (0x1 << 1), + SOUND_WAVE = (0x4 << 2), + SOUND_NOISE = (0x8 << 3), +} SoundChannel; + +u16 +sound_volume(SoundChannel channels, u8 volume) { + volume = volume & 0x7; + channels = channels & 0xF; + return volume | (volume << 0x4) | (channels << 0x8) | (channels << 0xC); +} + +// Sound Direct Sound master bits. +#define SOUND_DMG25 0x0 +#define SOUND_DMG50 0x1 +#define SOUND_DMG100 0x2 +#define SOUND_DSOUND_RATIO_A (1 << 0x2) +#define SOUND_DSOUND_RATIO_B (1 << 0x3) +#define SOUND_DSOUND_LEFT_A (1 << 0x8) +#define SOUND_DSOUND_RIGHT_A (1 << 0x9) +#define SOUND_DSOUND_TIMER_A (1 << 0xA) +#define SOUND_DSOUND_RESET_A (1 << 0xB) +#define SOUND_DSOUND_LEFT_B (1 << 0xC) +#define SOUND_DSOUND_RIGHT_B (1 << 0xD) +#define SOUND_DSOUND_TIMER_B (1 << 0xE) +#define SOUND_DSOUND_RESET_B (1 << 0xF) + +// Sound status bits. +#define SOUND_ENABLE (1 << 0x7) + +// DMG square control bits. +#define SOUND_SQUARE_LEN(N) (N) +#define SOUND_SQUARE_DUTY(N) ((N) << 0x6) +#define SOUND_SQUARE_ENV_TIME(N) ((N) << 0x8) +#define SOUND_SQUARE_ENV_INC (1 << 0xB) +#define SOUND_SQUARE_ENV_VOL(N) ((N) << 0xC) + +// DMG square frequency bits. +#define SOUND_SQUARE_TIMED (1 << 0xE) +#define SOUND_SQUARE_RESET (1 << 0xF) + +typedef enum { + NOTE_C, + NOTE_C_SHARP, + NOTE_D, + NOTE_D_SHARP, + NOTE_E, + NOTE_F, + NOTE_F_SHARP, + NOTE_G, + NOTE_G_SHARP, + NOTE_A, + NOTE_A_SHARP, + NOTE_B, +} Note; + +u32 +sound_rate(Note note, u32 octave) { + const u32 base_rate[12] = { + 8013, 7566, 7144, 6742, // C , C#, D , D# + 6362, 6005, 5666, 5346, // E , F , F#, G + 5048, 4766, 4499, 4246 // G#, A , A#, B + }; + return 2048 - (base_rate[note] >> (4 + octave)); +} + +// +// Misc. +// + +// Custom VSync option. This will waste a lot of battery power, since the +// machine is not clocked down. Prefer using `bios_vblank_wait()` if interrupts +// are enabled. +static inline void +wait_vsync(void) { + while(DISP_VCOUNT >= 160); + while(DISP_VCOUNT < 160); +} #endif // GBAEXP_COMMON_H diff --git a/src/main.c b/src/main.c index ec2150a..30452e1 100644 --- a/src/main.c +++ b/src/main.c @@ -17,9 +17,6 @@ // BIOS calls // -int bios_vblank_wait(); -int bios_div(int num, int denom); - int hblank_counter = 0; void @@ -48,19 +45,95 @@ int main(void) { // Register interrupts. irq_init(); - irs_set(IRQ_VBLANK, irq_stub); + irs_set(IRQ_VBLANK, irs_stub); irs_set(IRQ_HBLANK, irs_hblank_func); + // turn sound on + SOUND_STATUS = SOUND_ENABLE; + SOUND_DMG_MASTER = sound_volume(SOUND_SQUARE1 | SOUND_SQUARE2, 7); + SOUND_DSOUND_MASTER = SOUND_DMG100; + + char *note_names[] = { + "C" , "C#" , "D" , "D#" , + "E" , "F" , "F#" , "G" , + "G#" , "A" , "A#" , "B" + }; int frame_counter = 0; + Note active_note = NOTE_C; + u8 octave = 0; + bool playing = false; + bool new_note = false; + u8 interval = 1; while(true) { bios_vblank_wait(); poll_keys(); txt_position(0, 1); txt_clear_line(); - txt_printf(" HBlank counter: %d\n", hblank_counter); - txt_clear_line(); - txt_printf(" Frame counter: %d\n", frame_counter); + + if (key_pressed(KEY_B)) { + active_note = NOTE_C; + new_note = true; + octave = 0; + } else if (key_pressed(KEY_A)) { + active_note = NOTE_D; + new_note = true; + octave = 0; + } else if (key_pressed(KEY_LEFT)) { + active_note = NOTE_E; + new_note = true; + octave = 0; + } else if (key_pressed(KEY_DOWN)) { + active_note = NOTE_F; + new_note = true; + octave = 0; + } else if (key_pressed(KEY_RIGHT)) { + active_note = NOTE_G; + new_note = true; + octave = 0; + } else if (key_pressed(KEY_UP)) { + active_note = NOTE_A; + new_note = true; + octave = 0; + } else if (key_pressed(KEY_L)) { + active_note = NOTE_B; + new_note = true; + octave = 0; + } else if (key_pressed(KEY_R)) { + active_note = NOTE_C; + new_note = true; + octave = 1; + } + if (key_pressed(KEY_START)) { + interval++; + } + if (key_pressed(KEY_SELECT)) { + interval--; + } + + if (key_curr > 0) { + txt_printf(" Playing: %s + %s\n", + note_names[active_note], + (note_names[(active_note + interval) % 12])); + playing = true; + } else { + playing = false; + } + + if (playing) { + if (new_note) { + SOUND_SQUARE1_CTRL = SOUND_SQUARE_ENV_VOL(4) | SOUND_SQUARE_ENV_TIME(0); + SOUND_SQUARE2_CTRL = SOUND_SQUARE_ENV_VOL(4) | SOUND_SQUARE_ENV_TIME(0); + SOUND_SQUARE1_FREQ = SOUND_SQUARE_RESET | sound_rate(active_note, octave); + SOUND_SQUARE2_FREQ = SOUND_SQUARE_RESET | sound_rate((active_note + interval) % 12, octave); + } + new_note = false; + } else { + SOUND_SQUARE1_CTRL = SOUND_SQUARE_ENV_VOL(0) | SOUND_SQUARE_ENV_TIME(0); + SOUND_SQUARE2_CTRL = SOUND_SQUARE_ENV_VOL(0) | SOUND_SQUARE_ENV_TIME(0); + SOUND_SQUARE1_FREQ = 0; + SOUND_SQUARE2_FREQ = 0; + } frame_counter++; update_button_sprites(); diff --git a/src/text.h b/src/text.h index c852fc2..f178608 100644 --- a/src/text.h +++ b/src/text.h @@ -61,7 +61,7 @@ txt_init(size_t bg, Color clr, size_t cb_idx) { // Load font data in video memory. Each character is unpacked into a tile of // 8 32bit values (4bpp), meaning that for the full ASCII set of 256 // characters, we need 8192 bytes of VRAM (8 * 4 * 256). - unpack_tiles(&bd_font, &CHARBLOCK_MEM[cb_idx], 256); + unpack_tiles(&bd_font, &TILE_MEM[cb_idx], 256); // Load palette color. PAL_BUFFER_BG[1] = clr; -- cgit v1.2.1