diff --git a/.gitignore b/.gitignore index bc2d5ddf..35d81191 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Ignore generated files in the libdragon FS /filesystem/FiraMonoBold.font64 +/filesystem/*.wav64 # Ignore external development tools /tools/* diff --git a/Makefile b/Makefile index 6d71483c..adee21ab 100644 --- a/Makefile +++ b/Makefile @@ -79,17 +79,27 @@ SRCS = \ FONTS = \ FiraMonoBold.ttf +SOUNDS = \ + cursorsound.wav \ + back.wav \ + enter.wav \ + error.wav \ + settings.wav + OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o,$(basename $(SRCS)))) MINIZ_OBJS = $(filter $(BUILD_DIR)/libs/miniz/%.o,$(OBJS)) SPNG_OBJS = $(filter $(BUILD_DIR)/libs/libspng/%.o,$(OBJS)) DEPS = $(OBJS:.o=.d) FILESYSTEM = \ - $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(FONTS:%.ttf=%.font64))) + $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(FONTS:%.ttf=%.font64))) \ + $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(SOUNDS:%.wav=%.wav64))) $(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second $(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-1FF -r 2026-2026 --ellipsis 2026,1 +$(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1 + $(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null)) @@ -97,6 +107,10 @@ $(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf @echo " [FONT] $@" @$(N64_MKFONT) $(MKFONT_FLAGS) -o $(FILESYSTEM_DIR) "$<" +$(FILESYSTEM_DIR)/%.wav64: $(ASSETS_DIR)/%.wav + @echo " [AUDIO] $@" + @$(N64_AUDIOCONV) $(AUDIOCONV_FLAGS) -o $(FILESYSTEM_DIR) "$<" + $(BUILD_DIR)/$(PROJECT_NAME).dfs: $(FILESYSTEM) $(BUILD_DIR)/menu/views/credits.o: .FORCE diff --git a/README.md b/README.md index 288b4941..21ff68be 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ An open source menu for N64 flashcarts. * Comprehensive ROM information display. * Real Time Clock support. * Music playback (MP3). +* Menu sound effects. ### Video showcase (as of Oct 12 2023) @@ -160,3 +161,9 @@ Once merged, they can be viewed [here](https://polprzewodnikowy.github.io/N64Fla - [mini.c](https://github.com/univrsal/mini.c) (BSD 2-Clause License) - [minimp3](https://github.com/lieff/minimp3) (CC0 1.0 Universal) - [miniz](https://github.com/richgel999/miniz) (MIT License) + +## Sounds +See [License](https://pixabay.com/en/service/license-summary/) for the following sounds: + - [Cursor sound](https://pixabay.com/en/sound-effects/click-buttons-ui-menu-sounds-effects-button-7-203601/) by Skyscraper_seven (Free to use) + - [Actions (Enter, back) sound](https://pixabay.com/en/sound-effects/menu-button-user-interface-pack-190041/) by Liecio (Free to use) + - [Error sound](https://pixabay.com/en/sound-effects/error-call-to-attention-129258/) by Universfield (Free to use) diff --git a/assets/back.wav b/assets/back.wav new file mode 100644 index 00000000..f8d4655e Binary files /dev/null and b/assets/back.wav differ diff --git a/assets/cursorsound.wav b/assets/cursorsound.wav new file mode 100644 index 00000000..e6b2a624 Binary files /dev/null and b/assets/cursorsound.wav differ diff --git a/assets/enter.wav b/assets/enter.wav new file mode 100644 index 00000000..ad88ca23 Binary files /dev/null and b/assets/enter.wav differ diff --git a/assets/error.wav b/assets/error.wav new file mode 100644 index 00000000..5ced329b Binary files /dev/null and b/assets/error.wav differ diff --git a/assets/settings.wav b/assets/settings.wav new file mode 100644 index 00000000..3e9a80f0 Binary files /dev/null and b/assets/settings.wav differ diff --git a/src/menu/components/context_menu.c b/src/menu/components/context_menu.c index ff6db4db..c4d0364a 100644 --- a/src/menu/components/context_menu.c +++ b/src/menu/components/context_menu.c @@ -1,5 +1,6 @@ #include "../components.h" #include "../fonts.h" +#include "../sound.h" #include "constants.h" @@ -41,6 +42,7 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) } else { cm->hide_pending = true; } + sound_play_effect(SFX_EXIT); } else if (menu->actions.enter) { if (cm->list[cm->selected].submenu) { cm->submenu = cm->list[cm->selected].submenu; @@ -51,16 +53,19 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) cm->list[cm->selected].action(menu, cm->list[cm->selected].arg); top->hide_pending = true; } + sound_play_effect(SFX_ENTER); } else if (menu->actions.go_up) { cm->selected -= 1; if (cm->selected < 0) { cm->selected = 0; } + sound_play_effect(SFX_CURSOR); } else if (menu->actions.go_down) { cm->selected += 1; if (cm->selected >= cm->count) { cm->selected = (cm->count - 1); } + sound_play_effect(SFX_CURSOR); } return true; diff --git a/src/menu/menu.c b/src/menu/menu.c index a89282d5..3ffd070b 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -115,6 +115,10 @@ static void menu_init (boot_params_t *boot_params) { __boot_tvtype = TV_NTSC; } + if (menu->settings.sound_enabled) { + sound_init_sfx(); + } + display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_DISABLED); register_VI_handler(frame_counter_handler); diff --git a/src/menu/settings.c b/src/menu/settings.c index 3e046ed0..0b372531 100644 --- a/src/menu/settings.c +++ b/src/menu/settings.c @@ -13,10 +13,10 @@ static settings_t init = { .show_protected_entries = false, .default_directory = "/", .use_saves_folder = true, + .sound_enabled = true, /* Beta feature flags (should always init to off) */ .bgm_enabled = false, - .sound_enabled = false, .rumble_enabled = false, }; @@ -39,10 +39,10 @@ void settings_load (settings_t *settings) { settings->show_protected_entries = mini_get_bool(ini, "menu", "show_protected_entries", init.show_protected_entries); settings->default_directory = strdup(mini_get_string(ini, "menu", "default_directory", init.default_directory)); settings->use_saves_folder = mini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder); + settings->sound_enabled = mini_get_bool(ini, "menu", "sound_enabled", init.sound_enabled); /* Beta feature flags, they might not be in the file */ settings->bgm_enabled = mini_get_bool(ini, "menu_beta_flag", "bgm_enabled", init.bgm_enabled); - settings->sound_enabled = mini_get_bool(ini, "menu_beta_flag", "sound_enabled", init.sound_enabled); settings->rumble_enabled = mini_get_bool(ini, "menu_beta_flag", "rumble_enabled", init.rumble_enabled); mini_free(ini); @@ -55,10 +55,10 @@ void settings_save (settings_t *settings) { mini_set_bool(ini, "menu", "show_protected_entries", settings->show_protected_entries); mini_set_string(ini, "menu", "default_directory", settings->default_directory); mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder); + mini_set_bool(ini, "menu", "sound_enabled", settings->sound_enabled); /* Beta feature flags, they should not save until production ready! */ // mini_set_bool(ini, "menu_beta_flag", "bgm_enabled", settings->bgm_enabled); - // mini_set_bool(ini, "menu_beta_flag", "sound_enabled", settings->sound_enabled); // mini_set_bool(ini, "menu_beta_flag", "rumble_enabled", settings->rumble_enabled); mini_save(ini, MINI_FLAGS_SKIP_EMPTY_GROUPS); diff --git a/src/menu/sound.c b/src/menu/sound.c index 9e58d001..bf950d6c 100644 --- a/src/menu/sound.c +++ b/src/menu/sound.c @@ -3,14 +3,18 @@ #include #include "mp3_player.h" +#include "sound.h" #define DEFAULT_FREQUENCY (44100) #define NUM_BUFFERS (4) -#define NUM_CHANNELS (2) +#define NUM_CHANNELS (3) + +static wav64_t sfx_cursor, sfx_error, sfx_enter, sfx_exit, sfx_setting; static bool sound_initialized = false; +static bool sfx_enabled = false; static void sound_reconfigure (int frequency) { @@ -35,6 +39,43 @@ void sound_init_mp3_playback (void) { sound_reconfigure(mp3player_get_samplerate()); } + +void sound_init_sfx (void) { + mixer_ch_set_vol(SOUND_SFX_CHANNEL, 0.5f, 0.5f); + wav64_open(&sfx_cursor, "rom:/cursorsound.wav64"); + wav64_open(&sfx_exit, "rom:/back.wav64"); + wav64_open(&sfx_setting, "rom:/settings.wav64"); + wav64_open(&sfx_enter, "rom:/enter.wav64"); + wav64_open(&sfx_error, "rom:/error.wav64"); + sfx_enabled = true; +} + + +void sound_play_effect(sound_effect_t sfx) { + if(sfx_enabled) { + switch (sfx) { + case SFX_CURSOR: + wav64_play(&sfx_cursor, SOUND_SFX_CHANNEL); + break; + case SFX_EXIT: + wav64_play(&sfx_exit, SOUND_SFX_CHANNEL); + break; + case SFX_SETTING: + wav64_play(&sfx_setting, SOUND_SFX_CHANNEL); + break; + case SFX_ENTER: + wav64_play(&sfx_enter, SOUND_SFX_CHANNEL); + break; + case SFX_ERROR: + wav64_play(&sfx_error, SOUND_SFX_CHANNEL); + break; + default: + break; + } + } +} + + void sound_deinit (void) { if (sound_initialized) { mixer_close(); diff --git a/src/menu/sound.h b/src/menu/sound.h index 44ecf6bc..cf311519 100644 --- a/src/menu/sound.h +++ b/src/menu/sound.h @@ -9,12 +9,22 @@ #define SOUND_MP3_PLAYER_CHANNEL (0) +#define SOUND_SFX_CHANNEL (2) + +typedef enum { + SFX_CURSOR, + SFX_ERROR, + SFX_ENTER, + SFX_EXIT, + SFX_SETTING, +} sound_effect_t; void sound_init_default (void); void sound_init_mp3_playback (void); +void sound_init_sfx (void); +void sound_play_effect(sound_effect_t sfx); void sound_deinit (void); void sound_poll (void); - #endif diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index ccd540d9..491b191e 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -6,6 +6,7 @@ #include "../fonts.h" #include "utils/fs.h" #include "views.h" +#include "../sound.h" static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL }; @@ -298,16 +299,19 @@ static void process (menu_t *menu) { if (menu->browser.selected < 0) { menu->browser.selected = 0; } + sound_play_effect(SFX_CURSOR); } else if (menu->actions.go_down) { menu->browser.selected += scroll_speed; if (menu->browser.selected >= menu->browser.entries) { menu->browser.selected = menu->browser.entries - 1; } + sound_play_effect(SFX_CURSOR); } menu->browser.entry = &menu->browser.list[menu->browser.selected]; } if (menu->actions.enter && menu->browser.entry) { + sound_play_effect(SFX_ENTER); switch (menu->browser.entry->type) { case ENTRY_TYPE_DIR: if (push_directory(menu, menu->browser.entry->name)) { @@ -342,10 +346,13 @@ static void process (menu_t *menu) { menu->browser.valid = false; menu_show_error(menu, "Couldn't open last directory"); } + sound_play_effect(SFX_EXIT); } else if (menu->actions.options && menu->browser.entry) { component_context_menu_show(&entry_context_menu); + sound_play_effect(SFX_SETTING); } else if (menu->actions.settings) { component_context_menu_show(&settings_context_menu); + sound_play_effect(SFX_SETTING); } } diff --git a/src/menu/views/credits.c b/src/menu/views/credits.c index dc1d0665..fb068e6b 100644 --- a/src/menu/views/credits.c +++ b/src/menu/views/credits.c @@ -1,5 +1,5 @@ #include "views.h" - +#include "../sound.h" #ifndef MENU_VERSION #define MENU_VERSION "Unknown" @@ -13,6 +13,7 @@ static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/error.c b/src/menu/views/error.c index a5c0b84a..07eb70d4 100644 --- a/src/menu/views/error.c +++ b/src/menu/views/error.c @@ -1,9 +1,11 @@ #include "views.h" +#include "../sound.h" static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } @@ -48,6 +50,7 @@ void view_error_display (menu_t *menu, surface_t *display) { } void menu_show_error (menu_t *menu, char *error_message) { + sound_play_effect(SFX_ERROR); menu->next_mode = MENU_MODE_ERROR; menu->error_message = error_message; } diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index f03f022b..0e63ebe6 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -1,4 +1,5 @@ #include +#include "../sound.h" #include "utils/fs.h" #include "views.h" @@ -50,6 +51,7 @@ static char *format_file_type (char *name, bool is_directory) { static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index 084cc073..41c1f443 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -1,9 +1,11 @@ #include "views.h" +#include "../sound.h" static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } @@ -17,8 +19,17 @@ static void draw (menu_t *menu, surface_t *d) { component_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "FLASHCART INFORMATION\n" + "\n" + "\n" + "This feature is not yet supported.\n\n" ); + // FIXME: Display: + // * cart_type + // * Firmware version + // * supported features (flashcart_features_t) + + component_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" diff --git a/src/menu/views/image_viewer.c b/src/menu/views/image_viewer.c index 21c153b3..d58d3743 100644 --- a/src/menu/views/image_viewer.c +++ b/src/menu/views/image_viewer.c @@ -1,4 +1,5 @@ #include +#include "../sound.h" #include "../png_decoder.h" #include "views.h" @@ -40,6 +41,7 @@ static void process (menu_t *menu) { } else { menu->next_mode = MENU_MODE_BROWSER; } + sound_play_effect(SFX_EXIT); } else if (menu->actions.enter && image) { if (show_message) { show_message = false; @@ -48,6 +50,7 @@ static void process (menu_t *menu) { } else { show_message = true; } + sound_play_effect(SFX_ENTER); } } diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 7216e97d..983d052a 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -1,6 +1,7 @@ #include "../cart_load.h" #include "../disk_info.h" #include "boot/boot.h" +#include "../sound.h" #include "views.h" @@ -34,8 +35,10 @@ static void process (menu_t *menu) { } else if (menu->actions.options && menu->load.rom_path) { load_pending = true; load_rom = true; + sound_play_effect(SFX_SETTING); } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index 60f8d114..589a6a59 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -1,6 +1,7 @@ #include "../cart_load.h" #include "boot/boot.h" #include "utils/fs.h" +#include "../sound.h" #include "views.h" @@ -36,6 +37,7 @@ static void process (menu_t *menu) { load_pending = true; } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 9695bbc4..437a43a3 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -1,6 +1,7 @@ #include "../cart_load.h" #include "../rom_info.h" #include "boot/boot.h" +#include "../sound.h" #include "views.h" @@ -198,8 +199,10 @@ static void process (menu_t *menu) { load_pending = true; } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } else if (menu->actions.options) { component_context_menu_show(&options_context_menu); + sound_play_effect(SFX_SETTING); } } diff --git a/src/menu/views/music_player.c b/src/menu/views/music_player.c index 7b3d9751..4de00756 100644 --- a/src/menu/views/music_player.c +++ b/src/menu/views/music_player.c @@ -42,11 +42,13 @@ static void process (menu_t *menu) { menu_show_error(menu, convert_error_message(err)); } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } else if (menu->actions.enter) { err = mp3player_toggle(); if (err != MP3PLAYER_OK) { menu_show_error(menu, convert_error_message(err)); } + sound_play_effect(SFX_ENTER); } else if (menu->actions.go_left || menu->actions.go_right) { int seconds = menu->actions.go_fast ? SEEK_SECONDS_FAST : SEEK_SECONDS; err = mp3player_seek(menu->actions.go_left ? (-seconds) : seconds); diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index caeb9305..d810f382 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -1,4 +1,5 @@ #include +#include "../sound.h" #include "views.h" // FIXME: add implementation! @@ -18,6 +19,7 @@ static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 5e9d1854..badb3696 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -1,3 +1,4 @@ +#include "../sound.h" #include "views.h" @@ -12,6 +13,7 @@ static const char *format_switch (bool state) { static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/system_info.c b/src/menu/views/system_info.c index 02b78f72..74ed4cbd 100644 --- a/src/menu/views/system_info.c +++ b/src/menu/views/system_info.c @@ -1,5 +1,6 @@ #include +#include "../sound.h" #include "views.h" @@ -28,6 +29,7 @@ static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/text_viewer.c b/src/menu/views/text_viewer.c index 6b688479..cf8b505f 100644 --- a/src/menu/views/text_viewer.c +++ b/src/menu/views/text_viewer.c @@ -3,6 +3,7 @@ #include "../components/constants.h" #include "../fonts.h" +#include "../sound.h" #include "utils/utils.h" #include "views.h" @@ -55,6 +56,7 @@ static void perform_vertical_scroll (int lines) { static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } else if (text) { if (menu->actions.go_up) { perform_vertical_scroll(menu->actions.go_fast ? -10 : -1);