From 4a0516a7aedc3de459d37f712445d05d2faf4329 Mon Sep 17 00:00:00 2001 From: Vincent Mistler Date: Sat, 17 Dec 2022 16:37:47 +0100 Subject: [PATCH] Adjust sound volume with select+up/down & add DMG color palette --- .gitignore | 3 ++ ext/i2s/i2s.c | 73 +++++++++++++++++++++++---- ext/i2s/i2s.h | 7 ++- inc/gbcolors.h | 11 ++++ src/main.c | 134 ++++++++++++++++++++++++++++++------------------- 5 files changed, 166 insertions(+), 62 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33b0859 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +uf2 +src/rom.c diff --git a/ext/i2s/i2s.c b/ext/i2s/i2s.c index 5bded89..4970cc9 100644 --- a/ext/i2s/i2s.c +++ b/ext/i2s/i2s.c @@ -24,6 +24,9 @@ #include "i2s.h" +/** + * return the default i2s context used to store information about the setup + */ i2s_config_t i2s_get_default_config(void) { i2s_config_t i2s_config = { .sample_freq = 44100, @@ -35,11 +38,16 @@ i2s_config_t i2s_get_default_config(void) { .dma_channel = 0, .dma_buf = NULL, .dma_trans_count = 0, + .volume = 0, }; return i2s_config; } +/** + * Initialize the I2S driver. Must be called before calling i2s_write or i2s_dma_write + * i2s_config: I2S context obtained by i2s_get_default_config() + */ void i2s_init(i2s_config_t *i2s_config) { uint8_t func=GPIO_FUNC_PIO0; // TODO: GPIO_FUNC_PIO0 for pio0 or GPIO_FUNC_PIO1 for pio1 gpio_set_function(i2s_config->data_pin, GPIO_FUNC_PIO0); @@ -52,17 +60,17 @@ void i2s_init(i2s_config_t *i2s_config) { audio_i2s_program_init(i2s_config->pio, i2s_config->sm , offset, i2s_config->data_pin , i2s_config->clock_pin_base); - // Set PIO clock + /* Set PIO clock */ uint32_t system_clock_frequency = clock_get_hz(clk_sys); uint32_t divider = system_clock_frequency * 4 / i2s_config->sample_freq; // avoid arithmetic overflow pio_sm_set_clkdiv_int_frac(i2s_config->pio, i2s_config->sm , divider >> 8u, divider & 0xffu); pio_sm_set_enabled(i2s_config->pio, i2s_config->sm, false); - // Allocate memory for the DMA buffer + /* Allocate memory for the DMA buffer */ i2s_config->dma_buf=malloc(i2s_config->dma_trans_count*sizeof(uint32_t)); - // Direct Memory Access setup + /* Direct Memory Access setup */ i2s_config->dma_channel = dma_claim_unused_channel(true); dma_channel_config dma_config = dma_channel_get_default_config(i2s_config->dma_channel); @@ -81,20 +89,67 @@ void i2s_init(i2s_config_t *i2s_config) { pio_sm_set_enabled(i2s_config->pio, i2s_config->sm , true); } +/** + * Write samples to I2S directly and wait for completion (blocking) + * i2s_config: I2S context obtained by i2s_get_default_config() + * sample: pointer to an array of len x 32 bits samples + * Each 32 bits sample contains 2x16 bits samples, + * one for the left channel and one for the right channel + * len: length of sample in 32 bits words + */ void i2s_write(const i2s_config_t *i2s_config,const int16_t *samples,const size_t len) { - for(size_t i=0;ipio, i2s_config->sm, (uint32_t)samples[i]); } } -// Write samples to DMA buffer and initiate DMA transfer (non blocking) +/** + * Write samples to DMA buffer and initiate DMA transfer (non blocking) + * i2s_config: I2S context obtained by i2s_get_default_config() + * sample: pointer to an array of dma_trans_count x 32 bits samples + */ void i2s_dma_write(i2s_config_t *i2s_config,const int16_t *samples) { - // Wait the completion of the previous DMA transfer + /* Wait the completion of the previous DMA transfer */ dma_channel_wait_for_finish_blocking(i2s_config->dma_channel); - // Copy samples into the DMA buffer - memcpy(i2s_config->dma_buf,samples,i2s_config->dma_trans_count*sizeof(int32_t)); - // Initiate the DMA transfer + /* Copy samples into the DMA buffer */ + if(i2s_config->volume==0) { + memcpy(i2s_config->dma_buf,samples,i2s_config->dma_trans_count*sizeof(int32_t)); + } else { + for(uint16_t i=0;idma_trans_count*2;i++) { + i2s_config->dma_buf[i] = samples[i]>>i2s_config->volume; + } + } + + /* Initiate the DMA transfer */ dma_channel_transfer_from_buffer_now(i2s_config->dma_channel, i2s_config->dma_buf, i2s_config->dma_trans_count); } + +/** + * Adjust the output volume + * i2s_config: I2S context obtained by i2s_get_default_config() + * volume: desired volume between 0 (highest. volume) and 16 (lowest volume) + */ +void i2s_volume(i2s_config_t *i2s_config,uint8_t volume) { + if(volume>16) volume=16; + i2s_config->volume=volume; +} + +/** + * Increases the output volume + */ +void i2s_increase_volume(i2s_config_t *i2s_config) { + if(i2s_config->volume>0) { + i2s_config->volume--; + } +} + +/** + * Decreases the output volume + */ +void i2s_decrease_volume(i2s_config_t *i2s_config) { + if(i2s_config->volume<16) { + i2s_config->volume++; + } +} diff --git a/ext/i2s/i2s.h b/ext/i2s/i2s.h index 618ce11..b85621e 100644 --- a/ext/i2s/i2s.h +++ b/ext/i2s/i2s.h @@ -42,11 +42,14 @@ typedef struct i2s_config uint8_t dma_channel; uint16_t dma_trans_count; uint16_t *dma_buf; - + uint8_t volume; } i2s_config_t; i2s_config_t i2s_get_default_config(void); void i2s_init(i2s_config_t *i2s_config); void i2s_write(const i2s_config_t *i2s_config,const int16_t *samples,const size_t len); -void i2s_dma_write(i2s_config_t *i2s_config,const int16_t *samples); \ No newline at end of file +void i2s_dma_write(i2s_config_t *i2s_config,const int16_t *samples); +void i2s_volume(i2s_config_t *i2s_config,uint8_t volume); +void i2s_increase_volume(i2s_config_t *i2s_config); +void i2s_decrease_volume(i2s_config_t *i2s_config); diff --git a/inc/gbcolors.h b/inc/gbcolors.h index 174f303..970ad01 100644 --- a/inc/gbcolors.h +++ b/inc/gbcolors.h @@ -559,6 +559,17 @@ void get_colour_palette(palette_t selected_palette,uint8_t table_entry,uint8_t s memcpy(selected_palette, palette, PALETTE_SIZE_IN_BYTES); return; } + if(table_entry==0xFF && shuffling_flags==0xFF) + { + /* Game Boy DMG palette (4 shades of green) */ + const palette_t palette = { + { 0x7C02, 0x5BC8, 0x3AC9, 0x2A07 }, /* OBJ0 */ + { 0x7C02, 0x5BC8, 0x3AC9, 0x2A07 }, /* OBJ1 */ + { 0x7C02, 0x5BC8, 0x3AC9, 0x2A07 } /* BG */ + }; + memcpy(selected_palette, palette, PALETTE_SIZE_IN_BYTES); + return; + } /* default palette */ printf("get_colour_palette: No palette found for table_entry=0x%02X shuffling_flags=0x%02X\n", table_entry, diff --git a/src/main.c b/src/main.c index 7a9b96c..380d866 100644 --- a/src/main.c +++ b/src/main.c @@ -71,7 +71,6 @@ #define GPIO_B 7 #define GPIO_SELECT 8 #define GPIO_START 9 -#define GPIO_BUZZER 15 #define GPIO_CS 17 #define GPIO_CLK 18 #define GPIO_SDA 19 @@ -102,6 +101,18 @@ static uint8_t ram[32768]; static int lcd_line_busy = 0; static palette_t palette; // Colour palette +static struct +{ + unsigned a : 1; + unsigned b : 1; + unsigned select : 1; + unsigned start : 1; + unsigned right : 1; + unsigned left : 1; + unsigned up : 1; + unsigned down : 1; +} prev_joypad_bits; + /* Multicore command structure. */ union core_cmd { struct { @@ -249,7 +260,7 @@ _Noreturn void main_core1(void) { static dma_channel_config c2; - static const uint16_t clear_screen_colour = 0x0000; + uint16_t clear_screen_colour = palette[2][3]; union core_cmd cmd; /* Initialise and control LCD on core 1. */ @@ -387,7 +398,6 @@ int main(void) gpio_set_function(GPIO_B, GPIO_FUNC_SIO); gpio_set_function(GPIO_SELECT, GPIO_FUNC_SIO); gpio_set_function(GPIO_START, GPIO_FUNC_SIO); - gpio_set_function(GPIO_BUZZER, GPIO_FUNC_PWM); gpio_set_function(GPIO_CS, GPIO_FUNC_SIO); gpio_set_function(GPIO_CLK, GPIO_FUNC_SPI); gpio_set_function(GPIO_SDA, GPIO_FUNC_SPI); @@ -403,7 +413,6 @@ int main(void) gpio_set_dir(GPIO_B, false); gpio_set_dir(GPIO_SELECT, false); gpio_set_dir(GPIO_START, false); - gpio_set_dir(GPIO_BUZZER, true); gpio_set_dir(GPIO_CS, true); gpio_set_dir(GPIO_RS, true); gpio_set_dir(GPIO_RST, true); @@ -427,12 +436,6 @@ int main(void) spi_init(spi0, 30*1000*1000); spi_set_format(spi0, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); - /* Start Core1, which processes requests to the LCD. */ -#if ENABLE_LCD - putstdio("CORE1 "); - multicore_launch_core1(main_core1); -#endif - /* Initialise GB context. */ memcpy(rom_bank0, rom, sizeof(rom_bank0)); ret = gb_init(&gb, &gb_rom_read, &gb_cart_ram_read, @@ -445,9 +448,56 @@ int main(void) goto sleep; } + /* Update buttons state */ + gb.direct.joypad_bits.up=gpio_get(GPIO_UP); + gb.direct.joypad_bits.down=gpio_get(GPIO_DOWN); + gb.direct.joypad_bits.left=gpio_get(GPIO_LEFT); + gb.direct.joypad_bits.right=gpio_get(GPIO_RIGHT); + gb.direct.joypad_bits.a=gpio_get(GPIO_A); + gb.direct.joypad_bits.b=gpio_get(GPIO_B); + gb.direct.joypad_bits.select=gpio_get(GPIO_SELECT); + gb.direct.joypad_bits.start=gpio_get(GPIO_START); + + /* manually assign a palette with button combo */ + if(!gb.direct.joypad_bits.right) { + get_colour_palette(palette,0x05,0x00); /* Right */ + } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.down) { + get_colour_palette(palette,0x07,0x00); /* A + Down */ + } else if(!gb.direct.joypad_bits.up) { + get_colour_palette(palette,0x12,0x00); /* Up */ + } else if(!gb.direct.joypad_bits.b && !gb.direct.joypad_bits.right) { + get_colour_palette(palette,0x13,0x00); /* B + Right */ + } else if(!gb.direct.joypad_bits.b && !gb.direct.joypad_bits.left) { + get_colour_palette(palette,0x16,0x00); /* B + Left (Game Boy Pocket Palette, shades of gray) */ + } else if(!gb.direct.joypad_bits.down) { + get_colour_palette(palette,0x17,0x00); /* Down */ + } else if(!gb.direct.joypad_bits.b && !gb.direct.joypad_bits.up) { + get_colour_palette(palette,0x19,0x03); /* B + Up */ + } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.right) { + get_colour_palette(palette,0x1C,0x03); /* A + Right */ + } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.left) { + get_colour_palette(palette,0x0D,0x05); /* A + Left */ + } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.up) { + get_colour_palette(palette,0x10,0x05); /* A + Up */ + } else if(!gb.direct.joypad_bits.left) { + get_colour_palette(palette,0x18,0x05); /* Left */ + } else if(!gb.direct.joypad_bits.b && !gb.direct.joypad_bits.down) { + get_colour_palette(palette,0x1A,0x05); /* B + Down */ + } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.b) { + get_colour_palette(palette,0xFF,0xFF); /* A + B */ + } else { + /* Automatically assign a colour palette to the game */ + char rom_title[16]; + auto_assign_palette(palette, gb_colour_hash(&gb),gb_get_rom_name(&gb,rom_title)); + } + #if ENABLE_LCD gb_init_lcd(&gb, &lcd_draw_line); { + /* Start Core1, which processes requests to the LCD. */ + putstdio("CORE1 "); + multicore_launch_core1(main_core1); + /* initialise pixel buffer copy DMA. */ static dma_channel_config dma_config; gb_priv.dma_pixel_buffer_chan = dma_claim_unused_channel(true); @@ -486,6 +536,7 @@ int main(void) i2s_config_t i2s_config = i2s_get_default_config(); i2s_config.sample_freq=AUDIO_SAMPLE_RATE; i2s_config.dma_trans_count =AUDIO_SAMPLES; + i2s_volume(&i2s_config,2); i2s_init(&i2s_config); // Initialize audio emulation @@ -494,47 +545,6 @@ int main(void) putstdio("AUDIO "); #endif - /* Update buttons state */ - gb.direct.joypad_bits.up=gpio_get(GPIO_UP); - gb.direct.joypad_bits.down=gpio_get(GPIO_DOWN); - gb.direct.joypad_bits.left=gpio_get(GPIO_LEFT); - gb.direct.joypad_bits.right=gpio_get(GPIO_RIGHT); - gb.direct.joypad_bits.a=gpio_get(GPIO_A); - gb.direct.joypad_bits.b=gpio_get(GPIO_B); - gb.direct.joypad_bits.select=gpio_get(GPIO_SELECT); - gb.direct.joypad_bits.start=gpio_get(GPIO_START); - - /* manually assign a palette with button combo */ - if(!gb.direct.joypad_bits.right) { - get_colour_palette(palette,0x05,0x00); /* Right */ - } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.down) { - get_colour_palette(palette,0x07,0x00); /* A + Down */ - } else if(!gb.direct.joypad_bits.up) { - get_colour_palette(palette,0x12,0x00); /* Up */ - } else if(!gb.direct.joypad_bits.b && !gb.direct.joypad_bits.right) { - get_colour_palette(palette,0x13,0x00); /* B + Right */ - } else if(!gb.direct.joypad_bits.b && !gb.direct.joypad_bits.left) { - get_colour_palette(palette,0x16,0x00); /* B + Left (Game Boy Pocket Palette, shades of gray) */ - } else if(!gb.direct.joypad_bits.down) { - get_colour_palette(palette,0x17,0x00); /* Down */ - } else if(!gb.direct.joypad_bits.b && !gb.direct.joypad_bits.up) { - get_colour_palette(palette,0x19,0x03); /* B + Up */ - } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.right) { - get_colour_palette(palette,0x1C,0x03); /* A + Right */ - } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.left) { - get_colour_palette(palette,0x0D,0x05); /* A + Left */ - } else if(!gb.direct.joypad_bits.a && !gb.direct.joypad_bits.up) { - get_colour_palette(palette,0x10,0x05); /* A + Up */ - } else if(!gb.direct.joypad_bits.left) { - get_colour_palette(palette,0x18,0x05); /* Left */ - } else if(!gb.direct.joypad_bits.b && !gb.direct.joypad_bits.down) { - get_colour_palette(palette,0x1A,0x05); /* B + Down */ - } else { - /* Automatically assign a colour palette to the game */ - char rom_title[16]; - auto_assign_palette(palette, gb_colour_hash(&gb),gb_get_rom_name(&gb,rom_title)); - } - putstdio("\n> "); uint_fast32_t frames = 0; uint64_t start_time = time_us_64(); @@ -556,6 +566,14 @@ int main(void) #endif /* Update buttons state */ + prev_joypad_bits.up=gb.direct.joypad_bits.up; + prev_joypad_bits.down=gb.direct.joypad_bits.down; + prev_joypad_bits.left=gb.direct.joypad_bits.left; + prev_joypad_bits.right=gb.direct.joypad_bits.right; + prev_joypad_bits.a=gb.direct.joypad_bits.a; + prev_joypad_bits.b=gb.direct.joypad_bits.b; + prev_joypad_bits.select=gb.direct.joypad_bits.select; + prev_joypad_bits.start=gb.direct.joypad_bits.start; gb.direct.joypad_bits.up=gpio_get(GPIO_UP); gb.direct.joypad_bits.down=gpio_get(GPIO_DOWN); gb.direct.joypad_bits.left=gpio_get(GPIO_LEFT); @@ -565,6 +583,20 @@ int main(void) gb.direct.joypad_bits.select=gpio_get(GPIO_SELECT); gb.direct.joypad_bits.start=gpio_get(GPIO_START); + /* hotkeys (select + * combo)*/ + if(!gb.direct.joypad_bits.select) { +#if ENABLE_SOUND + if(!gb.direct.joypad_bits.up && prev_joypad_bits.up) { + /* select + up */ + i2s_increase_volume(&i2s_config); + } + if(!gb.direct.joypad_bits.down && prev_joypad_bits.down) { + /* select + down */ + i2s_decrease_volume(&i2s_config); + } +#endif + } + /* Serial monitor commands */ input = getchar_timeout_us(0); if(input == PICO_ERROR_TIMEOUT)