From 52592e93f23e5850ac839afe842fd3eca2dde000 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 30 Mar 2025 13:09:30 +0000 Subject: [PATCH 01/18] Fix libfmt flockfile/funlockfile link issue --- 3rdParty/libfmt/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdParty/libfmt/CMakeLists.txt b/3rdParty/libfmt/CMakeLists.txt index 48936347d4e..6ff1525615a 100644 --- a/3rdParty/libfmt/CMakeLists.txt +++ b/3rdParty/libfmt/CMakeLists.txt @@ -34,6 +34,6 @@ if(TARGET_PLATFORM STREQUAL "rg99" OR PLATFORM_DREAMCAST) endif() # https://github.com/fmtlib/fmt/issues/4189 -if(NINTENDO_3DS OR NINTENDO_SWITCH OR VITA) +if(NINTENDO_3DS OR NINTENDO_SWITCH OR VITA OR PLATFORM_DREAMCAST) target_compile_definitions(fmt PUBLIC FMT_USE_FALLBACK_FILE=1) endif() From cb45edffa4b029343655ac8fb711e86fcb8ffad3 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 30 Mar 2025 13:09:55 +0000 Subject: [PATCH 02/18] Enable sound and add memory usage logging --- CMake/platforms/dreamcast.cmake | 15 ++++++-- Source/effects.cpp | 10 ++++- Source/engine/sound.cpp | 11 ++++++ Source/engine/sound.h | 4 ++ Source/memory_stats.cpp | 65 +++++++++++++++++++++++++++++++++ Source/memory_stats.h | 11 ++++++ 6 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 Source/memory_stats.cpp create mode 100644 Source/memory_stats.h diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 3161c23f131..e151bdde968 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -10,7 +10,7 @@ set(DEFAULT_WIDTH 640) set(DEFAULT_HEIGHT 480) set(DEVILUTIONX_GAMEPAD_TYPE Nintendo) -set(NOSOUND ON) +#set(NOSOUND ON) set(DEVILUTIONX_STATIC_ZLIB ON) set(UNPACKED_MPQS ON) set(UNPACKED_SAVES ON) @@ -21,7 +21,7 @@ set(DEVILUTIONX_DISABLE_STRIP ON) set(DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/data/") set(BUILD_ASSETS_MPQ OFF) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/threads-stub") +#list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/threads-stub") list(APPEND DEVILUTIONX_PLATFORM_COMPILE_DEFINITIONS __DREAMCAST__) add_compile_options(-fpermissive) @@ -47,4 +47,13 @@ set(JOY_BUTTON_START 3) set(SDL_INCLUDE_DIR /usr/include/SDL/) set(SDL_LIBRARY /usr/lib/libSDL.a) -add_compile_options(-flto=none) +add_compile_options(-flto=auto) + +# Must stream most of the audio due to RAM constraints. +set(STREAM_ALL_AUDIO_MIN_FILE_SIZE 1023) + +# Must use a smaller audio buffer due to RAM constraints. +set(DEFAULT_AUDIO_BUFFER_SIZE 768) + +# Use lower resampling quality for FPS. +set(DEFAULT_AUDIO_RESAMPLING_QUALITY 1) diff --git a/Source/effects.cpp b/Source/effects.cpp index 1df897a0916..8807da12b8c 100644 --- a/Source/effects.cpp +++ b/Source/effects.cpp @@ -261,8 +261,16 @@ void effects_cleanup_sfx() void sound_init() { +#ifdef __DREAMCAST__ + bool isDreamcast = true; +#else + bool isDreamcast = false; +#endif uint8_t mask = sfx_MISC; - if (gbIsMultiplayer) { + // only load the SFX of the current character on the dreamcast + // because it still doesn't have network multiplayer mode + // so we only need the sfx of the player's class + if (gbIsMultiplayer && !isDreamcast) { mask |= sfx_WARRIOR; if (!gbIsSpawn) mask |= (sfx_ROGUE | sfx_SORCERER); diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index b9125881849..e778983a5da 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -24,6 +24,11 @@ #include "utils/str_cat.hpp" #include "utils/stubs.h" +// todo remove this +// changing this value in dreamcast.cmake causes the whole project to recompile +// redefined here to only recompile sound.cpp +#define STREAM_ALL_AUDIO_MIN_FILE_SIZE 10 * 1024 + namespace devilution { bool gbSndInited; @@ -71,6 +76,11 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample #endif #endif +#ifdef __DREAMCAST__ + Log(">AUDIO: Loading audio file {} with streaming {} ({} kilobytes)", foundPath, stream, ref.size() / 1024.0); + print_ram_stats(); + Log("\n\n\n"); +#endif if (stream) { if (result.SetChunkStream(foundPath, isMp3, /*logErrors=*/true) != 0) { if (errorDialog) { @@ -95,6 +105,7 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample return false; } const int error = result.SetChunk(waveFile, size, isMp3); + if (error != 0) { if (errorDialog) ErrSdl(); diff --git a/Source/engine/sound.h b/Source/engine/sound.h index 6ca09631499..fc0ed630f04 100644 --- a/Source/engine/sound.h +++ b/Source/engine/sound.h @@ -13,6 +13,10 @@ #include "utils/attributes.h" #ifndef NOSOUND +#ifdef __DREAMCAST__ +#include "memory_stats.h" +#endif + #include "utils/soundsample.h" #endif diff --git a/Source/memory_stats.cpp b/Source/memory_stats.cpp new file mode 100644 index 00000000000..e8dd8ce7923 --- /dev/null +++ b/Source/memory_stats.cpp @@ -0,0 +1,65 @@ +// DREAMCAST memory stats related code + +#include "memory_stats.h" + +#include +#include + +#include +#include + +static unsigned long systemRam = 0x00000000; +static unsigned long elfOffset = 0x00000000; +static unsigned long stackSize = 0x00000000; + +extern unsigned long end; +extern unsigned long start; + +#define _end end +#define _start start +void print_VRAM_stats() +{ + pvr_mem_available(); +} + +void set_system_ram() +{ + systemRam = 0x8d000000 - 0x8c000000; + elfOffset = 0x8c000000; + + stackSize = (int)&_end - (int)&_start + ((int)&_start - elfOffset); +} + +unsigned long get_system_ram() +{ + return systemRam; +} + +unsigned long get_free_ram() +{ + struct mallinfo mi = mallinfo(); + return systemRam - (mi.usmblks + stackSize); +} + +void print_ram_stats() +{ + float sys_ram, free_ram, used_ram, pvr_ram, sound_ram; + sys_ram = (float)get_system_ram() / (float)(1024 * 1024); + free_ram = (float)get_free_ram() / (float)(1024 * 1024); + used_ram = (sys_ram - free_ram); + // pvr_ram = (float)pvr_mem_available() / (float)(1024*1024); + sound_ram = (float)snd_mem_available() / (float)(1024 * 1024); + + printf("\n---------\nRAM stats (MB):\nTotal: %.2f, Free: %.2f, Used: %.2f, PVR: %.2f, Sound: %.2f\n---------\n", sys_ram, free_ram, used_ram, pvr_ram, sound_ram); + // printf("\n---------\nRAM stats (MB):\nTotal: %.2f, Free: %.2f, Used: %.2f\n---------\n", sys_ram, free_ram, used_ram); +} + +void get_ram_stats(float *sys_ram, float *free_ram, float *used_ram, float *pvr_ram) +{ + *sys_ram = (float)get_system_ram() / (float)(1024 * 1024); + *free_ram = (float)get_free_ram() / (float)(1024 * 1024); + *used_ram = (*sys_ram - *free_ram); + if (pvr_ram) { + //*pvr_ram = (float)pvr_mem_available() / (float)(1024*1024); + } +} diff --git a/Source/memory_stats.h b/Source/memory_stats.h new file mode 100644 index 00000000000..895007b70fa --- /dev/null +++ b/Source/memory_stats.h @@ -0,0 +1,11 @@ +#ifndef __MEMORY_STATS_H__ +#define __MEMORY_STATS_H__ 1 + +void set_system_ram(); +void print_VRAM_stats(); +unsigned long get_system_ram(); +unsigned long get_free_ram(); +void print_ram_stats(); +void get_ram_stats(float *sys_ram, float *free_ram, float *used_ram, float *pvr_ram); + +#endif From dc965947bc1e8c18cf1407d07b1ccaf54441c5df Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 30 Mar 2025 15:56:35 +0000 Subject: [PATCH 03/18] Update KOS docker image to include support for and/or/not operators --- .github/workflows/dreamcast.yml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 3c929007ed4..e9eb2a48800 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -25,7 +25,7 @@ concurrency: jobs: build: runs-on: ubuntu-latest - container: azihassan/kallistios:docker + container: azihassan/kallistios:8c7fbfcf3c38c4da82bc067e1719b74c2f93f755 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/Dockerfile b/Dockerfile index 6c327a9fa6f..002668bd18b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM azihassan/kallistios:fdffe33635239d46bcccf0d5c4d59bb7d2d91f38 +FROM azihassan/kallistios:8c7fbfcf3c38c4da82bc067e1719b74c2f93f755 RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ From 6b4eb1320405e20ad6f569e7fa066e64845f4582 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 31 Mar 2025 05:09:21 +0000 Subject: [PATCH 04/18] Fix memory_stats linking error --- Source/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index f291841044c..804a38b9c43 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -281,6 +281,9 @@ if(DEVILUTIONX_SCREENSHOT_FORMAT STREQUAL DEVILUTIONX_SCREENSHOT_FORMAT_PNG) utils/surface_to_png.cpp ) endif() +if(PLATFORM_DREAMCAST) + list(APPEND libdevilutionx_SRCS memory_stats.cpp) +endif() add_devilutionx_library(libdevilutionx OBJECT ${libdevilutionx_SRCS}) target_include_directories(libdevilutionx PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) From 1f71e4318eae9076ff0273d725543c66780cba9d Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 31 Mar 2025 15:36:00 +0000 Subject: [PATCH 05/18] Add memory_stats initialization code --- Source/diablo.cpp | 7 +++++++ Source/engine/sound.cpp | 4 ++++ Source/engine/sound.h | 1 + 3 files changed, 12 insertions(+) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index d8974ce86fc..183c94967ad 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -106,6 +106,10 @@ #include #endif +#ifdef __DREAMCAST__ +#include "memory_stats.h" +#endif + namespace devilution { uint32_t DungeonSeeds[NUMLEVELS]; @@ -2543,6 +2547,9 @@ void setOnInitialized(void (*callback)()) int DiabloMain(int argc, char **argv) { +#ifdef __DREAMCAST__ + set_system_ram(); +#endif #ifdef _DEBUG SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG); #endif diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index e778983a5da..0609d44cd7b 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -215,6 +215,9 @@ TSnd::~TSnd() void snd_init() { +#ifdef __DREAMCAST__ + ::snd_init(); +#endif sgOptions.Audio.soundVolume.SetValue(CapVolume(*sgOptions.Audio.soundVolume)); gbSoundOn = *sgOptions.Audio.soundVolume > VOLUME_MIN; sgbSaveSoundOn = gbSoundOn; @@ -239,6 +242,7 @@ void snd_init() void snd_deinit() { if (gbSndInited) { + snd_shutdown(); Aulib::quit(); duplicateSoundsMutex = std::nullopt; } diff --git a/Source/engine/sound.h b/Source/engine/sound.h index fc0ed630f04..ebd0945cf09 100644 --- a/Source/engine/sound.h +++ b/Source/engine/sound.h @@ -15,6 +15,7 @@ #ifndef NOSOUND #ifdef __DREAMCAST__ #include "memory_stats.h" +#include #endif #include "utils/soundsample.h" From b599bd07b73604db56e7f38dbb8031869ada55d0 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Wed, 2 Apr 2025 04:29:05 +0000 Subject: [PATCH 06/18] Patch KOS for streaming a large quantity of files --- .github/workflows/dreamcast.yml | 8 +++++++- Dockerfile | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index e9eb2a48800..ccc1d8e4d47 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -32,6 +32,12 @@ jobs: with: fetch-depth: 0 + - name: Patch KOS for streaming a large quantity of files + run: | + source /opt/toolchains/dc/kos/environ.sh && \ + cd /opt/toolchains/dc/kos && \ + make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096" + - name: Build unpack_and_minify_mpq run: | git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ @@ -82,7 +88,7 @@ jobs: - name: Build DevilutionX run: | - source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make + source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make -j4 - name: Generate .cdi run: | diff --git a/Dockerfile b/Dockerfile index 002668bd18b..0b906f13470 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,10 @@ FROM azihassan/kallistios:8c7fbfcf3c38c4da82bc067e1719b74c2f93f755 +RUN echo "Patching KOS for streaming a large quantity of files" +RUN cd /opt/toolchains/dc/kos && \ + make clean && \ + make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096" + RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ cd devilutionx-mpq-tools && \ From 0a53a188a5fef50604e5349c01aa3c92ed16dec2 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Thu, 3 Apr 2025 00:23:03 +0000 Subject: [PATCH 07/18] Increase thread stack size to 256 kilobytes for mp3 playback support --- .github/workflows/dreamcast.yml | 2 +- Dockerfile | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index ccc1d8e4d47..d6a0ed9a30b 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -36,7 +36,7 @@ jobs: run: | source /opt/toolchains/dc/kos/environ.sh && \ cd /opt/toolchains/dc/kos && \ - make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096" + make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DTHD_KERNEL_STACK_SIZE=262144" - name: Build unpack_and_minify_mpq run: | diff --git a/Dockerfile b/Dockerfile index 0b906f13470..0ea5a534b00 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,10 @@ FROM azihassan/kallistios:8c7fbfcf3c38c4da82bc067e1719b74c2f93f755 RUN echo "Patching KOS for streaming a large quantity of files" -RUN cd /opt/toolchains/dc/kos && \ +RUN source /opt/toolchains/dc/kos/environ.sh && \ + cd /opt/toolchains/dc/kos && \ make clean && \ - make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096" + make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DTHD_KERNEL_STACK_SIZE=262144" RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ From bf55975a8fd3f15586d8606859ecbc7deba79721 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Thu, 3 Apr 2025 02:31:51 +0000 Subject: [PATCH 08/18] Hardcode thread stack size values with sed --- .github/workflows/dreamcast.yml | 6 +++++- Dockerfile | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index d6a0ed9a30b..ec2e133825f 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -36,7 +36,11 @@ jobs: run: | source /opt/toolchains/dc/kos/environ.sh && \ cd /opt/toolchains/dc/kos && \ - make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DTHD_KERNEL_STACK_SIZE=262144" + sed -i 's/THD_KERNEL_STACK_SIZE (64 \* 1024)/THD_KERNEL_STACK_SIZE (256 \* 1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ + echo "THD_KERNEL_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_KERNEL_STACK_SIZE)" && \ + sed -i 's/THD_STACK_SIZE 32768/THD_STACK_SIZE (128*1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ + echo "THD_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_STACK_SIZE)" && \ + make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096" - name: Build unpack_and_minify_mpq run: | diff --git a/Dockerfile b/Dockerfile index 0ea5a534b00..65dbc859d0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,12 @@ FROM azihassan/kallistios:8c7fbfcf3c38c4da82bc067e1719b74c2f93f755 RUN echo "Patching KOS for streaming a large quantity of files" RUN source /opt/toolchains/dc/kos/environ.sh && \ cd /opt/toolchains/dc/kos && \ + sed -i 's/THD_KERNEL_STACK_SIZE (64 \* 1024)/THD_KERNEL_STACK_SIZE (256 \* 1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ + echo "THD_KERNEL_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_KERNEL_STACK_SIZE)" && \ + sed -i 's/THD_STACK_SIZE 32768/THD_STACK_SIZE (128*1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ + echo "THD_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_STACK_SIZE)" && \ make clean && \ - make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DTHD_KERNEL_STACK_SIZE=262144" + make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096" RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ From 9d90c908d18318555523f582960d348717ae2caa Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Wed, 9 Apr 2025 00:09:22 +0100 Subject: [PATCH 09/18] Replace sdl_audiolib parts with snd_sfx_ and libwav --- CMakeLists.txt | 4 ++ Source/engine/sound.cpp | 37 +++++++++-- Source/engine/sound.h | 2 + Source/utils/soundsample.cpp | 121 ++++++++++++++++++++++++++++++++--- Source/utils/soundsample.h | 79 ++++++++++++++++++++++- 5 files changed, 227 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52ec92a517f..1a8a14e53f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -519,6 +519,10 @@ if(NXDK) add_custom_target(nxdk_xbe DEPENDS "${_xbe_path}") endif() +if(PLATFORM_DREAMCAST) + target_link_libraries(${BIN_TARGET} PRIVATE libwav.a) +endif() + if(CPACK AND (APPLE OR BUILD_ASSETS_MPQ OR SRC_DIST)) if(WIN32) if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index 0609d44cd7b..70b43e36506 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -27,7 +27,7 @@ // todo remove this // changing this value in dreamcast.cmake causes the whole project to recompile // redefined here to only recompile sound.cpp -#define STREAM_ALL_AUDIO_MIN_FILE_SIZE 10 * 1024 +#define STREAM_ALL_AUDIO_MIN_FILE_SIZE 25 * 1024 namespace devilution { @@ -82,7 +82,11 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample Log("\n\n\n"); #endif if (stream) { +#ifdef __DREAMCAST__ + if (result.SetChunkStream(ref.path, isMp3, /*logErrors=*/true) != 0) { +#else if (result.SetChunkStream(foundPath, isMp3, /*logErrors=*/true) != 0) { +#endif if (errorDialog) { ErrDlg("Failed to load audio file", StrCat(foundPath, "\n", SDL_GetError(), "\n"), __FILE__, __LINE__); } @@ -98,6 +102,9 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample ErrDlg("Failed to load audio file", StrCat(foundPath, "\n", SDL_GetError(), "\n"), __FILE__, __LINE__); return false; } +#ifdef __DREAMCAST__ + const int error = result.SetChunk(ref.path, size, isMp3); +#else auto waveFile = MakeArraySharedPtr(size); if (!handle.read(waveFile.get(), size)) { if (errorDialog) @@ -105,6 +112,7 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample return false; } const int error = result.SetChunk(waveFile, size, isMp3); +#endif if (error != 0) { if (errorDialog) @@ -215,9 +223,6 @@ TSnd::~TSnd() void snd_init() { -#ifdef __DREAMCAST__ - ::snd_init(); -#endif sgOptions.Audio.soundVolume.SetValue(CapVolume(*sgOptions.Audio.soundVolume)); gbSoundOn = *sgOptions.Audio.soundVolume > VOLUME_MIN; sgbSaveSoundOn = gbSoundOn; @@ -225,6 +230,13 @@ void snd_init() sgOptions.Audio.musicVolume.SetValue(CapVolume(*sgOptions.Audio.musicVolume)); gbMusicOn = *sgOptions.Audio.musicVolume > VOLUME_MIN; +#ifdef __DREAMCAST__ + gbMusicOn = true; + // spu_init(); + //::snd_init(); + snd_stream_init(); + assert(1 == wav_init()); +#else // Initialize the SDL_audiolib library. Set the output sample rate to // 22kHz, the audio format to 16-bit signed, use 2 output channels // (stereo), and a 2KiB output buffer. @@ -235,6 +247,7 @@ void snd_init() LogVerbose(LogCategory::Audio, "Aulib sampleRate={} channels={} frameSize={} format={:#x}", Aulib::sampleRate(), Aulib::channelCount(), Aulib::frameSize(), Aulib::sampleFormat()); +#endif duplicateSoundsMutex.emplace(); gbSndInited = true; } @@ -242,8 +255,13 @@ void snd_init() void snd_deinit() { if (gbSndInited) { - snd_shutdown(); +#ifdef __DREAMCAST__ + wav_shutdown(); + snd_stream_shutdown(); + //::snd_shutdown(); +#else Aulib::quit(); +#endif duplicateSoundsMutex = std::nullopt; } @@ -274,12 +292,14 @@ _music_id GetLevelMusic(dungeon_type dungeonType) void music_stop() { + Log("music_stop()"); music.Release(); sgnMusicTrack = NUM_MUSIC; } void music_start(_music_id nTrack) { + Log("music_start()"); const char *trackPath; assert(nTrack < NUM_MUSIC); @@ -291,12 +311,15 @@ void music_start(_music_id nTrack) else trackPath = MusicTracks[nTrack]; + Log(" trackPath = {}", trackPath); #ifdef DISABLE_STREAMING_MUSIC const bool stream = false; #else const bool stream = true; #endif + Log(" stream = {}", stream); if (!LoadAudioFile(trackPath, stream, /*errorDialog=*/false, music)) { + Log(" LoadAudioFile failed"); music_stop(); return; } @@ -310,7 +333,9 @@ void music_start(_music_id nTrack) return; } + Log(" music.Play()"); sgnMusicTrack = nTrack; + Log(" sgnMusicTrack = {}", (int)sgnMusicTrack); } void sound_disable_music(bool disable) @@ -347,12 +372,14 @@ int sound_get_or_set_sound_volume(int volume) void music_mute() { + Log("music_mute"); if (music.IsLoaded()) music.Mute(); } void music_unmute() { + Log("music_unmute"); if (music.IsLoaded()) music.Unmute(); } diff --git a/Source/engine/sound.h b/Source/engine/sound.h index ebd0945cf09..55b87a39075 100644 --- a/Source/engine/sound.h +++ b/Source/engine/sound.h @@ -16,6 +16,8 @@ #ifdef __DREAMCAST__ #include "memory_stats.h" #include +#include +#include #endif #include "utils/soundsample.h" diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index ac34d536969..64acb8abbd1 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -93,8 +93,22 @@ float VolumeLogToLinear(int logVolume, int logMin, int logMax) void SoundSample::Release() { +#ifdef __DREAMCAST__ + if (stream_ != SND_STREAM_INVALID) { + wav_destroy(stream_); + stream_ = SND_STREAM_INVALID; + } + if (file_data_ != SFXHND_INVALID) { + snd_sfx_unload(file_data_); + file_data_ = SFXHND_INVALID; + channel_ = -1; + } + volume_ = 0; + previousVolume_ = 0; +#else stream_ = nullptr; file_data_ = nullptr; +#endif file_data_size_ = 0; } @@ -103,27 +117,72 @@ void SoundSample::Release() */ bool SoundSample::IsPlaying() { - return stream_ && stream_->isPlaying(); + if (IsStreaming()) { +#ifdef __DREAMCAST__ + Log("wav_is_playing({}) = {}", file_path_, wav_is_playing(stream_)); + return wav_is_playing(stream_); +#else + return stream_->isPlaying(); +#endif + } else { + return snd_is_playing(channel_); + } } bool SoundSample::Play(int numIterations) { - if (!stream_->play(numIterations)) { + if (IsStreaming()) { +#ifdef __DREAMCAST__ + Log("Streaming {} with audio 255", file_path_); + wav_volume(stream_, 255); + wav_play(stream_); + wav_volume(stream_, 255); + return true; +#else + if (!stream_->play(numIterations)) { + LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); + return false; + } + return true; +#endif + } + volume_ = 255; + pan_ = 128; + Log("snd_sfx_play({}, {}, {})", file_path_, volume_, pan_); + int channel = snd_sfx_play(file_data_, volume_, pan_); + if (channel == -1) { LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); return false; } + channel_ = channel; return true; } int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors) { +#ifdef __DREAMCAST__ + stream_ = wav_create(filePath.c_str(), 1); + if (stream_ == SND_STREAM_INVALID) { + if (logErrors) + LogError(LogCategory::Audio, "wav_create failed (from SoundSample::SetChunkStream) for {}", filePath); + return -1; + } + file_path_ = filePath; + isMp3_ = isMp3; + file_data_ = SFXHND_INVALID; + volume_ = 255; + pan_ = 128; + wav_volume(stream_, volume_); + snd_stream_pan(stream_, 128, 128); + return 0; +#else SDL_RWops *handle = OpenAssetAsSdlRwOps(filePath.c_str(), /*threadsafe=*/true); if (handle == nullptr) { if (logErrors) LogError(LogCategory::Audio, "OpenAsset failed (from SoundSample::SetChunkStream) for {}: {}", filePath, SDL_GetError()); return -1; } - file_path_ = std::move(filePath); + file_path_ = filePath; isMp3_ = isMp3; stream_ = CreateStream(handle, isMp3); if (!stream_->open()) { @@ -133,8 +192,28 @@ int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors return -1; } return 0; +#endif } +#ifdef __DREAMCAST__ +int SoundSample::SetChunk(std::string filePath, std::size_t dwBytes, bool isMp3) +{ + isMp3_ = isMp3; + file_path_ = filePath; + Log("snd_sfx_load({})", filePath); + file_data_ = snd_sfx_load(filePath.c_str()); + file_data_size_ = dwBytes; + if (file_data_ == SFXHND_INVALID) { + LogError(LogCategory::Audio, "snd_sfx_load returned -1 for {}", filePath); + return -1; + } + + volume_ = 255; + pan_ = 128; + stream_ = SND_STREAM_INVALID; + return 0; +} +#else int SoundSample::SetChunk(ArraySharedPtr fileData, std::size_t dwBytes, bool isMp3) { isMp3_ = isMp3; @@ -155,22 +234,48 @@ int SoundSample::SetChunk(ArraySharedPtr fileData, std::size_t dwB return 0; } +#endif void SoundSample::SetVolume(int logVolume, int logMin, int logMax) { - stream_->setVolume(VolumeLogToLinear(logVolume, logMin, logMax)); + if (IsStreaming()) { +#ifdef __DREAMCAST__ + previousVolume_ = volume_; + volume_ = VolumeLogToLinear(logVolume, logMin, logMax); + wav_volume(stream_, 255); +#else + stream_->setVolume(VolumeLogToLinear(logVolume, logMin, logMax)); +#endif + } else { + previousVolume_ = volume_; + volume_ = VolumeLogToLinear(logVolume, logMin, logMax); + } } void SoundSample::SetStereoPosition(int logPan) { - stream_->setStereoPosition(PanLogToLinear(logPan)); + if (IsStreaming()) { +#ifdef __DREAMCAST__ + Log("pan is not supported in libwav on the Dreamcast"); + snd_stream_pan(stream_, 128, 128); +#else + stream_->setStereoPosition(PanLogToLinear(logPan)); +#endif + } else { + pan_ = PanLogToLinear(logPan); + } } int SoundSample::GetLength() const { - if (!stream_) - return 0; - return static_cast(std::chrono::duration_cast(stream_->duration()).count()); +#ifdef __DREAMCAST__ + if (IsStreaming()) + return 10000; +#else + if (IsStreaming()) + return static_cast(std::chrono::duration_cast(stream_->duration()).count()); +#endif + return 1000; } } // namespace devilution diff --git a/Source/utils/soundsample.h b/Source/utils/soundsample.h index 5673d6479cd..b92b3e25ce8 100644 --- a/Source/utils/soundsample.h +++ b/Source/utils/soundsample.h @@ -7,8 +7,16 @@ #include #include "engine/sound_defs.hpp" +#include "utils/log.hpp" #include "utils/stdcompat/shared_ptr_array.hpp" +#ifdef __DREAMCAST__ +#include +#include +#include +#include +#endif + namespace devilution { class SoundSample final { @@ -19,7 +27,11 @@ class SoundSample final { [[nodiscard]] bool IsLoaded() const { +#ifdef __DREAMCAST__ + return stream_ != SND_STREAM_INVALID || file_data_ != SFXHND_INVALID; +#else return stream_ != nullptr; +#endif } void Release(); @@ -30,7 +42,11 @@ class SoundSample final { void SetFinishCallback(Aulib::Stream::Callback &&callback) { +#ifdef __DREAMCAST__ + Log("SetFinishCallback not implemented yet"); +#else stream_->setFinishCallback(std::forward(callback)); +#endif } /** @@ -40,18 +56,31 @@ class SoundSample final { * @param isMp3 Whether the data is an MP3 * @return 0 on success, -1 otherwise */ +#ifdef __DREAMCAST__ + int SetChunk(std::string filePath, std::size_t dwBytes, bool isMp3); +#else int SetChunk(ArraySharedPtr fileData, std::size_t dwBytes, bool isMp3); +#endif [[nodiscard]] bool IsStreaming() const { + Log("IsStreaming {}: {}", file_path_, stream_ != SND_STREAM_INVALID); +#ifdef __DREAMCAST__ + return stream_ != SND_STREAM_INVALID; +#else return file_data_ == nullptr; +#endif } int DuplicateFrom(const SoundSample &other) { if (other.IsStreaming()) return SetChunkStream(other.file_path_, other.isMp3_); +#ifdef __DREAMCAST__ + return SetChunk(other.file_path_, other.file_data_size_, other.isMp3_); +#else return SetChunk(other.file_data_, other.file_data_size_, other.isMp3_); +#endif } /** @@ -74,7 +103,17 @@ class SoundSample final { */ void Stop() { - stream_->stop(); + if (IsStreaming()) { +#ifdef __DREAMCAST__ + Log("wav_stop({})", file_path_); + wav_stop(stream_); +#else + stream_->stop(); +#endif + } else { + Log("snd_sfx_stop({})", file_path_); + snd_sfx_stop(channel_); + } } void SetVolume(int logVolume, int logMin, int logMax); @@ -82,12 +121,34 @@ class SoundSample final { void Mute() { - stream_->mute(); + if (IsStreaming()) { +#ifdef __DREAMCAST__ + previousVolume_ = volume_; + volume_ = 0; + wav_volume(stream_, volume_); +#else + stream_->mute(); +#endif + } else { + previousVolume_ = volume_; + volume_ = 0; + } } void Unmute() { - stream_->unmute(); + if (IsStreaming()) { +#ifdef __DREAMCAST__ + volume_ = previousVolume_; + previousVolume_ = 0; + wav_volume(stream_, volume_); +#else + stream_->unmute(); +#endif + } else { + volume_ = previousVolume_; + previousVolume_ = 0; + } } /** @@ -97,7 +158,15 @@ class SoundSample final { private: // Non-streaming audio fields: +#ifdef __DREAMCAST__ + sfxhnd_t file_data_ = SFXHND_INVALID; + int channel_ = -1; + int volume_ = 0; + int previousVolume_ = 0; + int pan_ = 128; +#else ArraySharedPtr file_data_; +#endif std::size_t file_data_size_; // Set for streaming audio to allow for duplicating it: @@ -105,7 +174,11 @@ class SoundSample final { bool isMp3_; +#ifdef __DREAMCAST__ + wav_stream_hnd_t stream_ = SND_STREAM_INVALID; +#else std::unique_ptr stream_; +#endif }; } // namespace devilution From 46f9eb468264ec8ceb3df94d071a459a40fe4a16 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Wed, 9 Apr 2025 00:31:48 +0100 Subject: [PATCH 10/18] Use newer kallistios image --- .github/workflows/dreamcast.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index ec2e133825f..d2903d554d4 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -25,7 +25,7 @@ concurrency: jobs: build: runs-on: ubuntu-latest - container: azihassan/kallistios:8c7fbfcf3c38c4da82bc067e1719b74c2f93f755 + container: azihassan/kallistios:fdffe33635239d46bcccf0d5c4d59bb7d2d91f38 steps: - name: Checkout uses: actions/checkout@v4 From e90bac47515d7c1b47bb347675b7ce42c00f00bf Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Wed, 9 Apr 2025 00:44:37 +0100 Subject: [PATCH 11/18] Fix MAKEIP_VERSION kos build error --- .github/workflows/dreamcast.yml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index d2903d554d4..d69a8c2bac3 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -40,7 +40,7 @@ jobs: echo "THD_KERNEL_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_KERNEL_STACK_SIZE)" && \ sed -i 's/THD_STACK_SIZE 32768/THD_STACK_SIZE (128*1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ echo "THD_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_STACK_SIZE)" && \ - make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096" + make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DMAKEIP_VERSION=2" - name: Build unpack_and_minify_mpq run: | diff --git a/Dockerfile b/Dockerfile index 65dbc859d0f..da99135b491 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN source /opt/toolchains/dc/kos/environ.sh && \ sed -i 's/THD_STACK_SIZE 32768/THD_STACK_SIZE (128*1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ echo "THD_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_STACK_SIZE)" && \ make clean && \ - make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096" + make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DMAKEIP_VERSION=2" RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ From 9d4eebe598555d4f10c35ce50de2f7ca0734b48d Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Wed, 9 Apr 2025 11:21:19 +0100 Subject: [PATCH 12/18] Use latest KOS and remove mp3-related stack size modifications --- .github/workflows/dreamcast.yml | 6 +----- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index d69a8c2bac3..69b093cdf31 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -25,7 +25,7 @@ concurrency: jobs: build: runs-on: ubuntu-latest - container: azihassan/kallistios:fdffe33635239d46bcccf0d5c4d59bb7d2d91f38 + container: azihassan/kallistios:1b9c7b6bc4858d947a12675c58122951dd42c4db steps: - name: Checkout uses: actions/checkout@v4 @@ -36,10 +36,6 @@ jobs: run: | source /opt/toolchains/dc/kos/environ.sh && \ cd /opt/toolchains/dc/kos && \ - sed -i 's/THD_KERNEL_STACK_SIZE (64 \* 1024)/THD_KERNEL_STACK_SIZE (256 \* 1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ - echo "THD_KERNEL_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_KERNEL_STACK_SIZE)" && \ - sed -i 's/THD_STACK_SIZE 32768/THD_STACK_SIZE (128*1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ - echo "THD_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_STACK_SIZE)" && \ make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DMAKEIP_VERSION=2" - name: Build unpack_and_minify_mpq diff --git a/Dockerfile b/Dockerfile index da99135b491..7d1460cfb69 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM azihassan/kallistios:8c7fbfcf3c38c4da82bc067e1719b74c2f93f755 +FROM azihassan/kallistios:1b9c7b6bc4858d947a12675c58122951dd42c4db RUN echo "Patching KOS for streaming a large quantity of files" RUN source /opt/toolchains/dc/kos/environ.sh && \ From 252e0cb95d3e4f42bb8f1a7e8c5a37813580ac14 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Wed, 9 Apr 2025 12:26:09 +0100 Subject: [PATCH 13/18] Undo latest vmu-related changes in KOS until vmu_pkg_parse is fixed --- .github/workflows/dreamcast.yml | 1 + Dockerfile | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 69b093cdf31..468e68fd658 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -36,6 +36,7 @@ jobs: run: | source /opt/toolchains/dc/kos/environ.sh && \ cd /opt/toolchains/dc/kos && \ + git reset --hard dca7f6d86be234b6488bd9d7e05aef0fa10f8d96 && \ make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DMAKEIP_VERSION=2" - name: Build unpack_and_minify_mpq diff --git a/Dockerfile b/Dockerfile index 7d1460cfb69..50291dcecbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,10 +3,7 @@ FROM azihassan/kallistios:1b9c7b6bc4858d947a12675c58122951dd42c4db RUN echo "Patching KOS for streaming a large quantity of files" RUN source /opt/toolchains/dc/kos/environ.sh && \ cd /opt/toolchains/dc/kos && \ - sed -i 's/THD_KERNEL_STACK_SIZE (64 \* 1024)/THD_KERNEL_STACK_SIZE (256 \* 1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ - echo "THD_KERNEL_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_KERNEL_STACK_SIZE)" && \ - sed -i 's/THD_STACK_SIZE 32768/THD_STACK_SIZE (128*1024)/g' /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h && \ - echo "THD_STACK_SIZE changed to $(cat /opt/toolchains/dc/kos/kernel/arch/dreamcast/include/arch/arch.h | grep THD_STACK_SIZE)" && \ + git reset --hard dca7f6d86be234b6488bd9d7e05aef0fa10f8d96 && \ make clean && \ make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DMAKEIP_VERSION=2" From c46e0411de087b77ec913de51c543905a90369ef Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Tue, 15 Apr 2025 00:46:01 +0100 Subject: [PATCH 14/18] Fix streaming issue caused by assert line --- Source/engine/sound.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index 70b43e36506..8b6f90624a0 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -27,7 +27,7 @@ // todo remove this // changing this value in dreamcast.cmake causes the whole project to recompile // redefined here to only recompile sound.cpp -#define STREAM_ALL_AUDIO_MIN_FILE_SIZE 25 * 1024 +#define STREAM_ALL_AUDIO_MIN_FILE_SIZE 22 * 1024 namespace devilution { @@ -231,11 +231,16 @@ void snd_init() gbMusicOn = *sgOptions.Audio.musicVolume > VOLUME_MIN; #ifdef __DREAMCAST__ + printf("snd_init() in sound.cpp\n"); gbMusicOn = true; // spu_init(); - //::snd_init(); + ::snd_init(); snd_stream_init(); - assert(1 == wav_init()); + // assert(1 == wav_init()); + if (!wav_init()) { + LogError(LogCategory::Audio, "Failed to initialize audio (wav_init)"); + return; + } #else // Initialize the SDL_audiolib library. Set the output sample rate to // 22kHz, the audio format to 16-bit signed, use 2 output channels @@ -255,10 +260,11 @@ void snd_init() void snd_deinit() { if (gbSndInited) { + printf("snd_deinit() in sound.cpp\n"); #ifdef __DREAMCAST__ wav_shutdown(); snd_stream_shutdown(); - //::snd_shutdown(); + ::snd_shutdown(); #else Aulib::quit(); #endif From 1835ea72bd5569b842e4b1cf969e6dbe04eef94f Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Tue, 15 Apr 2025 00:46:15 +0100 Subject: [PATCH 15/18] Include modified libwav code in project --- CMake/platforms/dreamcast.cmake | 1 + CMakeLists.txt | 6 +- Source/CMakeLists.txt | 6 +- Source/engine/sound.h | 2 +- Source/libwav.c | 164 +++++++++++ Source/libwav.h | 34 +++ Source/sndwav.c | 466 ++++++++++++++++++++++++++++++++ Source/sndwav.h | 31 +++ Source/utils/soundsample.cpp | 181 +++++++------ Source/utils/soundsample.h | 159 ++++++++--- 10 files changed, 920 insertions(+), 130 deletions(-) create mode 100644 Source/libwav.c create mode 100644 Source/libwav.h create mode 100644 Source/sndwav.c create mode 100644 Source/sndwav.h diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index e151bdde968..fb3b78ac723 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -48,6 +48,7 @@ set(SDL_INCLUDE_DIR /usr/include/SDL/) set(SDL_LIBRARY /usr/lib/libSDL.a) add_compile_options(-flto=auto) +#add_compile_options(-lwav) # Must stream most of the audio due to RAM constraints. set(STREAM_ALL_AUDIO_MIN_FILE_SIZE 1023) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a8a14e53f0..c3d93966af9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -519,9 +519,9 @@ if(NXDK) add_custom_target(nxdk_xbe DEPENDS "${_xbe_path}") endif() -if(PLATFORM_DREAMCAST) - target_link_libraries(${BIN_TARGET} PRIVATE libwav.a) -endif() +#if(PLATFORM_DREAMCAST) +# target_link_libraries(${BIN_TARGET} PRIVATE libwav.a) +#endif() if(CPACK AND (APPLE OR BUILD_ASSETS_MPQ OR SRC_DIST)) if(WIN32) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 804a38b9c43..57033c39dd8 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -282,7 +282,11 @@ if(DEVILUTIONX_SCREENSHOT_FORMAT STREQUAL DEVILUTIONX_SCREENSHOT_FORMAT_PNG) ) endif() if(PLATFORM_DREAMCAST) - list(APPEND libdevilutionx_SRCS memory_stats.cpp) + list(APPEND libdevilutionx_SRCS + memory_stats.cpp + libwav.c + sndwav.c + ) endif() add_devilutionx_library(libdevilutionx OBJECT ${libdevilutionx_SRCS}) diff --git a/Source/engine/sound.h b/Source/engine/sound.h index 55b87a39075..2905d3f9151 100644 --- a/Source/engine/sound.h +++ b/Source/engine/sound.h @@ -15,9 +15,9 @@ #ifndef NOSOUND #ifdef __DREAMCAST__ #include "memory_stats.h" +#include "sndwav.h" #include #include -#include #endif #include "utils/soundsample.h" diff --git a/Source/libwav.c b/Source/libwav.c new file mode 100644 index 00000000000..3efbb56832a --- /dev/null +++ b/Source/libwav.c @@ -0,0 +1,164 @@ +#include "libwav.h" + +#include + +#include + +typedef struct __attribute__((__packed__)) { + uint8_t riff[4]; + int32_t totalsize; + uint8_t riff_format[4]; +} wavmagic_t; + +typedef struct __attribute__((__packed__)) { + uint8_t id[4]; + size_t size; +} chunkhdr_t; + +typedef struct __attribute__((__packed__)) { + int16_t format; + int16_t channels; + int32_t sample_rate; + int32_t byte_per_sec; + int16_t blocksize; + int16_t sample_size; +} fmthdr_t; + +int wav_get_info_file(file_t file, WavFileInfo *result) +{ + wavmagic_t wavmagic; + chunkhdr_t chunkhdr; + fmthdr_t fmthdr; + + memset(result, 0, sizeof(WavFileInfo)); + + fs_seek(file, 0, SEEK_SET); + if (fs_read(file, &wavmagic, sizeof(wavmagic)) != sizeof(wavmagic)) { + fs_close(file); + return 0; + } + + if (strncmp((const char *)wavmagic.riff, "RIFF", 4)) { + fs_close(file); + return 0; + } + + if (strncmp((const char *)wavmagic.riff_format, "WAVE", 4)) { + fs_close(file); + return 0; + } + + do { + /* Read the chunk header */ + if (fs_read(file, &(chunkhdr), sizeof(chunkhdr)) != sizeof(chunkhdr)) { + fs_close(file); + return 0; + } + + /* If it is the fmt chunk, grab the fields we care about and skip the + rest of the section if there is more */ + if (strncmp((const char *)chunkhdr.id, "fmt ", 4) == 0) { + if (fs_read(file, &(fmthdr), sizeof(fmthdr)) != sizeof(fmthdr)) { + fs_close(file); + return 0; + } + + /* Skip the rest of the fmt chunk */ + fs_seek(file, chunkhdr.size - sizeof(fmthdr), SEEK_CUR); + } + /* If we found the data chunk, we are done */ + else if (strncmp((const char *)chunkhdr.id, "data", 4) == 0) { + break; + } + /* Skip meta data */ + else { + fs_seek(file, chunkhdr.size, SEEK_CUR); + } + } while (1); + + result->format = fmthdr.format; + result->channels = fmthdr.channels; + result->sample_rate = fmthdr.sample_rate; + result->sample_size = fmthdr.sample_size; + result->data_length = chunkhdr.size; + + result->data_offset = fs_tell(file); + + return 1; +} + +int wav_get_info_cdda(file_t file, WavFileInfo *result) +{ + result->format = WAVE_FORMAT_PCM; + result->channels = 2; + result->sample_rate = 44100; + result->sample_size = 16; + result->data_length = fs_total(file); + + result->data_offset = 0; + + return 1; +} + +int wav_get_info_adpcm(file_t file, WavFileInfo *result) +{ + result->format = WAVE_FORMAT_YAMAHA_ADPCM; + result->channels = 2; + result->sample_rate = 44100; + result->sample_size = 4; + result->data_length = fs_total(file); + + result->data_offset = 0; + + return 1; +} + +int wav_get_info_buffer(const uint8_t *buffer, WavFileInfo *result) +{ + wavmagic_t wavmagic; + chunkhdr_t chunkhdr; + fmthdr_t fmthdr; + size_t data_offset = sizeof(wavmagic_t); + + memset(result, 0, sizeof(WavFileInfo)); + memcpy(&wavmagic, buffer, sizeof(wavmagic_t)); + + if (strncmp((const char *)wavmagic.riff, "RIFF", 4)) + return 0; + + if (strncmp((const char *)wavmagic.riff_format, "WAVE", 4)) + return 0; + + do { + /* Read the next chunk header */ + memcpy(&(chunkhdr), buffer + data_offset, sizeof(chunkhdr)); + data_offset += sizeof(chunkhdr); + + /* If it is the fmt chunk, grab the fields we care about and skip the + rest of the section if there is more */ + if (strncmp((const char *)chunkhdr.id, "fmt ", 4) == 0) { + memcpy(&(fmthdr), buffer + data_offset, sizeof(fmthdr)); + + /* Skip the rest of the fmt chunk */ + data_offset += chunkhdr.size; + } + /* If we found the data chunk, we are done */ + else if (strncmp((const char *)chunkhdr.id, "data", 4) == 0) { + break; + } + /* Skip meta data */ + else { + data_offset += chunkhdr.size; + } + } while (1); + + result->format = fmthdr.format; + result->channels = fmthdr.channels; + result->sample_rate = fmthdr.sample_rate; + result->sample_size = fmthdr.sample_size; + result->data_length = chunkhdr.size; + + result->data_offset = data_offset; + + return 1; +} diff --git a/Source/libwav.h b/Source/libwav.h new file mode 100644 index 00000000000..e7cf2b08d68 --- /dev/null +++ b/Source/libwav.h @@ -0,0 +1,34 @@ +#ifndef LIBWAV_H +#define LIBWAV_H + +#include +__BEGIN_DECLS + +#include +#include + +#define WAVE_FORMAT_PCM 0x0001 /* PCM */ +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 /* IEEE float */ +#define WAVE_FORMAT_ALAW 0x0006 /* 8-bit ITU-T G.711 A-law */ +#define WAVE_FORMAT_MULAW 0x0007 /* 8-bit ITU-T G.711 ยต-law */ +#define WAVE_FORMAT_YAMAHA_ADPCM 0x0020 /* Yamaha ADPCM (ffmpeg) */ +#define WAVE_FORMAT_YAMAHA_ADPCM_ITU_G723 0x0014 /* ITU G.723 Yamaha ADPCM (KallistiOS) */ +#define WAVE_FORMAT_EXTENSIBLE 0xfffe /* Determined by SubFormat */ + +typedef struct { + uint32_t format; + uint32_t channels; + uint32_t sample_rate; + uint32_t sample_size; + uint32_t data_offset; + uint32_t data_length; +} WavFileInfo; + +int wav_get_info_file(file_t file, WavFileInfo *result); +int wav_get_info_cdda(file_t file, WavFileInfo *result); +int wav_get_info_adpcm(file_t file, WavFileInfo *result); +int wav_get_info_buffer(const uint8_t *buffer, WavFileInfo *result); + +__END_DECLS + +#endif diff --git a/Source/sndwav.c b/Source/sndwav.c new file mode 100644 index 00000000000..3fa142ebc74 --- /dev/null +++ b/Source/sndwav.c @@ -0,0 +1,466 @@ + +#include +#include +#include +#include + +#include +#include +#include + +#include "libwav.h" +#include "sndwav.h" + +/* Keep track of things from the Driver side */ +#define SNDDRV_STATUS_NULL 0x00 +#define SNDDRV_STATUS_READY 0x01 +#define SNDDRV_STATUS_DONE 0x02 + +/* Keep track of things from the Decoder side */ +#define SNDDEC_STATUS_NULL 0x00 +#define SNDDEC_STATUS_READY 0x01 +#define SNDDEC_STATUS_STREAMING 0x02 +#define SNDDEC_STATUS_PAUSING 0x03 +#define SNDDEC_STATUS_STOPPING 0x04 +#define SNDDEC_STATUS_RESUMING 0x05 + +// #define SND_STREAM_BUFFER_MAX_ADPCM 8 +// #define SND_STREAM_MAX 128 + +typedef void *(*snddrv_cb)(snd_stream_hnd_t, int, int *); + +typedef struct { + /* The buffer on the AICA side */ + snd_stream_hnd_t shnd; + + /* We either read the wav data from a file or + we read from a buffer */ + file_t wave_file; + const uint8_t *wave_buf; + + /* Contains the buffer that we are going to send + to the AICA in the callback. Should be 32-byte + aligned */ + uint8_t *drv_buf; + + /* Status of the stream that can be started, stopped + paused, ready. etc */ + volatile int status; + + snddrv_cb callback; + + uint32_t loop; + uint32_t vol; /* 0-255 */ + + uint32_t format; /* Wave format */ + uint32_t channels; /* 1-Mono/2-Stereo */ + uint32_t sample_rate; /* 44100Hz */ + uint32_t sample_size; /* 4/8/16-Bit */ + + /* Offset into the file or buffer where the audio + data starts */ + uint32_t data_offset; + + /* The length of the audio data */ + uint32_t data_length; + + /* Used only in reading wav data from a buffer + and not a file */ + uint32_t buf_offset; + +} snddrv_hnd; + +static snddrv_hnd streams[SND_STREAM_MAX]; +static volatile int sndwav_status = SNDDRV_STATUS_NULL; +static kthread_t *audio_thread; +static mutex_t stream_mutex = MUTEX_INITIALIZER; + +static void *sndwav_thread(void *param); +static void *wav_file_callback(snd_stream_hnd_t hnd, int req, int *done); +static void *wav_buf_callback(snd_stream_hnd_t hnd, int req, int *done); + +int wav_init(void) +{ + int i; + + if (snd_stream_init_ex(1, SND_STREAM_BUFFER_MAX_ADPCM) < 0) + return 0; + + for (i = 0; i < SND_STREAM_MAX; i++) { + streams[i].shnd = SND_STREAM_INVALID; + streams[i].vol = 255; + streams[i].status = SNDDEC_STATUS_NULL; + streams[i].callback = NULL; + } + + audio_thread = thd_create(0, sndwav_thread, NULL); + if (audio_thread != NULL) { + sndwav_status = SNDDRV_STATUS_READY; + printf("wav_init() = %d\n", 1); + return 1; + } else { + printf("wav_init() = %d\n", 0); + return 0; + } +} + +void wav_shutdown(void) +{ + int i; + + sndwav_status = SNDDRV_STATUS_DONE; + + thd_join(audio_thread, NULL); + + for (i = 0; i < SND_STREAM_MAX; i++) { + wav_destroy(i); + } +} + +void wav_destroy(wav_stream_hnd_t hnd) +{ + if (streams[hnd].shnd == SND_STREAM_INVALID) + return; + + mutex_lock(&stream_mutex); + + snd_stream_destroy(streams[hnd].shnd); + streams[hnd].shnd = SND_STREAM_INVALID; + streams[hnd].status = SNDDEC_STATUS_NULL; + streams[hnd].vol = 255; + streams[hnd].callback = NULL; + + if (streams[hnd].wave_file != FILEHND_INVALID) + fs_close(streams[hnd].wave_file); + + if (streams[hnd].drv_buf) { + free(streams[hnd].drv_buf); + streams[hnd].drv_buf = NULL; + } + + mutex_unlock(&stream_mutex); +} + +wav_stream_hnd_t wav_create(const char *filename, int loop) +{ + int fn_len; + file_t file; + WavFileInfo info; + wav_stream_hnd_t index; + + if (filename == NULL) + return SND_STREAM_INVALID; + + file = fs_open(filename, O_RDONLY); + + if (file == FILEHND_INVALID) + return SND_STREAM_INVALID; + + index = snd_stream_alloc(wav_file_callback, SND_STREAM_BUFFER_MAX_ADPCM); + + if (index == SND_STREAM_INVALID) { + fs_close(file); + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + fn_len = strlen(filename); + + /* Check for ".raw" or ".pcm" extension */ + if (fn_len >= 4 && ((strcmp(&filename[fn_len - 4], ".raw") == 0) || (strcmp(&filename[fn_len - 4], ".pcm") == 0))) { + wav_get_info_cdda(file, &info); + } + /* Check for ".adpcm" extension */ + else if (fn_len >= 6 && strcmp(&filename[fn_len - 6], ".adpcm") == 0) { + wav_get_info_adpcm(file, &info); + } + /* Default case: handle other file types */ + else if (!wav_get_info_file(file, &info)) { + fs_close(file); + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + streams[index].drv_buf = memalign(32, SND_STREAM_BUFFER_MAX_ADPCM); + + if (streams[index].drv_buf == NULL) { + fs_close(file); + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + streams[index].shnd = index; + streams[index].wave_file = file; + streams[index].loop = loop; + streams[index].callback = wav_file_callback; + + streams[index].format = info.format; + streams[index].channels = info.channels; + streams[index].sample_rate = info.sample_rate; + streams[index].sample_size = info.sample_size; + streams[index].data_length = info.data_length; + streams[index].data_offset = info.data_offset; + + fs_seek(streams[index].wave_file, streams[index].data_offset, SEEK_SET); + streams[index].status = SNDDEC_STATUS_READY; + + printf("wav_create(%s, %d) = %d\n", filename, loop, index); + return index; +} + +wav_stream_hnd_t wav_create_fd(file_t file, int loop) +{ + WavFileInfo info; + wav_stream_hnd_t index; + + if (file == FILEHND_INVALID) + return SND_STREAM_INVALID; + + index = snd_stream_alloc(wav_file_callback, SND_STREAM_BUFFER_MAX_ADPCM); + + if (index == SND_STREAM_INVALID) { + fs_close(file); + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + if (!wav_get_info_file(file, &info)) { + fs_close(file); + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + streams[index].drv_buf = memalign(32, SND_STREAM_BUFFER_MAX_ADPCM); + + if (streams[index].drv_buf == NULL) { + fs_close(file); + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + streams[index].shnd = index; + streams[index].wave_file = file; + streams[index].loop = loop; + streams[index].callback = wav_file_callback; + + streams[index].format = info.format; + streams[index].channels = info.channels; + streams[index].sample_rate = info.sample_rate; + streams[index].sample_size = info.sample_size; + streams[index].data_length = info.data_length; + streams[index].data_offset = info.data_offset; + + fs_seek(streams[index].wave_file, streams[index].data_offset, SEEK_SET); + streams[index].status = SNDDEC_STATUS_READY; + + return index; +} + +wav_stream_hnd_t wav_create_buf(const uint8_t *buf, int loop) +{ + WavFileInfo info; + wav_stream_hnd_t index; + + if (buf == NULL) + return SND_STREAM_INVALID; + + index = snd_stream_alloc(wav_file_callback, SND_STREAM_BUFFER_MAX_ADPCM); + + if (index == SND_STREAM_INVALID) { + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + if (!wav_get_info_buffer(buf, &info)) { + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + streams[index].drv_buf = memalign(32, SND_STREAM_BUFFER_MAX_ADPCM); + + if (streams[index].drv_buf == NULL) { + snd_stream_destroy(index); + return SND_STREAM_INVALID; + } + + streams[index].shnd = index; + streams[index].wave_buf = buf; + streams[index].loop = loop; + streams[index].callback = wav_buf_callback; + + streams[index].format = info.format; + streams[index].channels = info.channels; + streams[index].sample_rate = info.sample_rate; + streams[index].sample_size = info.sample_size; + streams[index].data_length = info.data_length; + streams[index].data_offset = info.data_offset; + + streams[index].buf_offset = info.data_offset; + streams[index].status = SNDDEC_STATUS_READY; + + return index; +} + +void wav_play(wav_stream_hnd_t hnd) +{ + if (streams[hnd].status == SNDDEC_STATUS_STREAMING) + return; + + streams[hnd].status = SNDDEC_STATUS_RESUMING; + printf("wav_play(%d) OK\n", hnd); + printf("streams[%d].status = %d\n", hnd, streams[hnd].status); +} + +void wav_pause(wav_stream_hnd_t hnd) +{ + if (streams[hnd].status == SNDDEC_STATUS_READY || streams[hnd].status == SNDDEC_STATUS_PAUSING) + return; + + streams[hnd].status = SNDDEC_STATUS_PAUSING; + printf("wav_pause(%d) OK\n", hnd); + printf("streams[%d].status = %d\n", hnd, streams[hnd].status); +} + +void wav_stop(wav_stream_hnd_t hnd) +{ + if (streams[hnd].status == SNDDEC_STATUS_READY || streams[hnd].status == SNDDEC_STATUS_STOPPING) + return; + + streams[hnd].status = SNDDEC_STATUS_STOPPING; + printf("wav_stop(%d) OK\n", hnd); + printf("streams[%d].status = %d\n", hnd, streams[hnd].status); +} + +void wav_volume(wav_stream_hnd_t hnd, int vol) +{ + if (streams[hnd].shnd == SND_STREAM_INVALID) + return; + + if (vol > 255) + vol = 255; + + if (vol < 0) + vol = 0; + + streams[hnd].vol = vol; + snd_stream_volume(streams[hnd].shnd, streams[hnd].vol); + printf("wav_volume(%d, %d) OK\n", hnd, vol); + printf("streams[%d].vol = %d\n", hnd, streams[hnd].vol); +} + +int wav_is_playing(wav_stream_hnd_t hnd) +{ + printf("wav_is_playing(%d) = %d\n", hnd, streams[hnd].status == SNDDEC_STATUS_STREAMING); + return streams[hnd].status == SNDDEC_STATUS_STREAMING; +} + +void wav_add_filter(wav_stream_hnd_t hnd, wav_filter filter, void *obj) +{ + snd_stream_filter_add(streams[hnd].shnd, filter, obj); +} + +void wav_remove_filter(wav_stream_hnd_t hnd, wav_filter filter, void *obj) +{ + snd_stream_filter_remove(streams[hnd].shnd, filter, obj); +} + +static void *sndwav_thread(void *param) +{ + (void)param; + int i; + + // printf("sndwav_thread\n"); + while (sndwav_status != SNDDRV_STATUS_DONE) { + + mutex_lock(&stream_mutex); + + for (i = 0; i < SND_STREAM_MAX; i++) { + // printf(" before: streams[%d].status = %d\n", i, streams[i].status); + switch (streams[i].status) { + case SNDDEC_STATUS_RESUMING: + printf(" before: streams[%d].status = %d\n", i, streams[i].status); + printf(" : streams[%d].sample_size = %d\n", i, streams[i].sample_size); + if (streams[i].sample_size == 16) { + snd_stream_start(streams[i].shnd, streams[i].sample_rate, streams[i].channels - 1); + } else if (streams[i].sample_size == 8) { + snd_stream_start_pcm8(streams[i].shnd, streams[i].sample_rate, streams[i].channels - 1); + } else if (streams[i].sample_size == 4) { + printf("snd_stream_start_adpcm(%d, %d, %d)\n", streams[i].shnd, streams[i].sample_rate, streams[i].channels - 1); + snd_stream_start_adpcm(streams[i].shnd, streams[i].sample_rate, streams[i].channels - 1); + } + snd_stream_volume(streams[i].shnd, streams[i].vol); + streams[i].status = SNDDEC_STATUS_STREAMING; + printf(" after: streams[%d].status = %d\n", i, streams[i].status); + break; + case SNDDEC_STATUS_PAUSING: + snd_stream_stop(streams[i].shnd); + streams[i].status = SNDDEC_STATUS_READY; + break; + case SNDDEC_STATUS_STOPPING: + snd_stream_stop(streams[i].shnd); + if (streams[i].wave_file != FILEHND_INVALID) + fs_seek(streams[i].wave_file, streams[i].data_offset, SEEK_SET); + else + streams[i].buf_offset = streams[i].data_offset; + + streams[i].status = SNDDEC_STATUS_READY; + break; + case SNDDEC_STATUS_STREAMING: + // printf("snd_stream_poll(%d)\n", streams[i].shnd); + snd_stream_poll(streams[i].shnd); + break; + case SNDDEC_STATUS_READY: + default: + break; + } + // printf(" after: streams[%d] = %d\n", i, streams[i].status); + } + + mutex_unlock(&stream_mutex); + + thd_sleep(50); + } + + return NULL; +} + +static void *wav_file_callback(snd_stream_hnd_t hnd, int req, int *done) +{ + int read = fs_read(streams[hnd].wave_file, streams[hnd].drv_buf, req); + + if (read != req) { + fs_seek(streams[hnd].wave_file, streams[hnd].data_offset, SEEK_SET); + if (streams[hnd].loop) { + fs_read(streams[hnd].wave_file, streams[hnd].drv_buf, req); + } else { + snd_stream_stop(streams[hnd].shnd); + streams[hnd].status = SNDDEC_STATUS_READY; + return NULL; + } + } + + *done = req; + + return streams[hnd].drv_buf; +} + +static void *wav_buf_callback(snd_stream_hnd_t hnd, int req, int *done) +{ + // printf("wav_buf_callback(%d, %d, %d)\n", hnd, req, *done); + if ((streams[hnd].data_length - (streams[hnd].buf_offset - streams[hnd].data_offset)) >= req) + memcpy(streams[hnd].drv_buf, streams[hnd].wave_buf + streams[hnd].buf_offset, req); + else { + streams[hnd].buf_offset = streams[hnd].data_offset; + if (streams[hnd].loop) { + memcpy(streams[hnd].drv_buf, streams[hnd].wave_buf + streams[hnd].buf_offset, req); + } else { + snd_stream_stop(streams[hnd].shnd); + streams[hnd].status = SNDDEC_STATUS_READY; + return NULL; + } + } + + streams[hnd].buf_offset += *done = req; + + return streams[hnd].drv_buf; +} diff --git a/Source/sndwav.h b/Source/sndwav.h new file mode 100644 index 00000000000..9a23684f890 --- /dev/null +++ b/Source/sndwav.h @@ -0,0 +1,31 @@ +#ifndef SNDWAV_H +#define SNDWAV_H + +#include +__BEGIN_DECLS + +#include + +typedef int wav_stream_hnd_t; +typedef void (*wav_filter)(wav_stream_hnd_t hnd, void *obj, int hz, int channels, void **buffer, int *samplecnt); + +int wav_init(void); +void wav_shutdown(void); +void wav_destroy(wav_stream_hnd_t hnd); + +wav_stream_hnd_t wav_create(const char *filename, int loop); +wav_stream_hnd_t wav_create_fd(file_t fd, int loop); +wav_stream_hnd_t wav_create_buf(const uint8_t *buf, int loop); + +void wav_play(wav_stream_hnd_t hnd); +void wav_pause(wav_stream_hnd_t hnd); +void wav_stop(wav_stream_hnd_t hnd); +void wav_volume(wav_stream_hnd_t hnd, int vol); +int wav_is_playing(wav_stream_hnd_t hnd); + +void wav_add_filter(wav_stream_hnd_t hnd, wav_filter filter, void *obj); +void wav_remove_filter(wav_stream_hnd_t hnd, wav_filter filter, void *obj); + +__END_DECLS + +#endif diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index 64acb8abbd1..b161fd56873 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -91,9 +91,94 @@ float VolumeLogToLinear(int logVolume, int logMin, int logMax) ///// SoundSample ///// +#ifndef __DREAMCAST__ void SoundSample::Release() { -#ifdef __DREAMCAST__ + stream_ = nullptr; + file_data_ = nullptr; + file_data_size_ = 0; +} + +/** + * @brief Check if a the sound is being played atm + */ +bool SoundSample::IsPlaying() +{ + return stream_ && stream_->isPlaying(); +} + +bool SoundSample::Play(int numIterations) +{ + if (!stream_->play(numIterations)) { + LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); + return false; + } + return true; +} + +int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors) +{ + SDL_RWops *handle = OpenAssetAsSdlRwOps(filePath.c_str(), /*threadsafe=*/true); + if (handle == nullptr) { + if (logErrors) + LogError(LogCategory::Audio, "OpenAsset failed (from SoundSample::SetChunkStream) for {}: {}", filePath, SDL_GetError()); + return -1; + } + file_path_ = std::move(filePath); + isMp3_ = isMp3; + stream_ = CreateStream(handle, isMp3); + if (!stream_->open()) { + stream_ = nullptr; + if (logErrors) + LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunkStream) for {}: {}", file_path_, SDL_GetError()); + return -1; + } + return 0; +} + +int SoundSample::SetChunk(ArraySharedPtr fileData, std::size_t dwBytes, bool isMp3) +{ + isMp3_ = isMp3; + file_data_ = std::move(fileData); + file_data_size_ = dwBytes; + SDL_RWops *buf = SDL_RWFromConstMem(file_data_.get(), static_cast(dwBytes)); + if (buf == nullptr) { + return -1; + } + + stream_ = CreateStream(buf, isMp3_); + if (!stream_->open()) { + stream_ = nullptr; + file_data_ = nullptr; + LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunk): {}", SDL_GetError()); + return -1; + } + + return 0; +} + +void SoundSample::SetVolume(int logVolume, int logMin, int logMax) +{ + stream_->setVolume(VolumeLogToLinear(logVolume, logMin, logMax)); +} + +void SoundSample::SetStereoPosition(int logPan) +{ + stream_->setStereoPosition(PanLogToLinear(logPan)); +} + +int SoundSample::GetLength() const +{ + if (!stream_) + return 0; + return static_cast(std::chrono::duration_cast(stream_->duration()).count()); +} + +#else + +void SoundSample::Release() +{ + // Log("SoundSample::Release({})", file_path_); if (stream_ != SND_STREAM_INVALID) { wav_destroy(stream_); stream_ = SND_STREAM_INVALID; @@ -105,10 +190,6 @@ void SoundSample::Release() } volume_ = 0; previousVolume_ = 0; -#else - stream_ = nullptr; - file_data_ = nullptr; -#endif file_data_size_ = 0; } @@ -117,13 +198,10 @@ void SoundSample::Release() */ bool SoundSample::IsPlaying() { + // Log("SoundSample::IsPlaying({})", file_path_); if (IsStreaming()) { -#ifdef __DREAMCAST__ - Log("wav_is_playing({}) = {}", file_path_, wav_is_playing(stream_)); + // Log("wav_is_playing({}) = {}", file_path_, wav_is_playing(stream_)); return wav_is_playing(stream_); -#else - return stream_->isPlaying(); -#endif } else { return snd_is_playing(channel_); } @@ -131,24 +209,15 @@ bool SoundSample::IsPlaying() bool SoundSample::Play(int numIterations) { + // Log("SoundSample::Play({}, {})", file_path_, numIterations); if (IsStreaming()) { -#ifdef __DREAMCAST__ Log("Streaming {} with audio 255", file_path_); wav_volume(stream_, 255); wav_play(stream_); - wav_volume(stream_, 255); - return true; -#else - if (!stream_->play(numIterations)) { - LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); - return false; - } return true; -#endif } volume_ = 255; - pan_ = 128; - Log("snd_sfx_play({}, {}, {})", file_path_, volume_, pan_); + // Log("snd_sfx_play({}, {}, {})", file_path_, volume_, pan_); int channel = snd_sfx_play(file_data_, volume_, pan_); if (channel == -1) { LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); @@ -160,8 +229,10 @@ bool SoundSample::Play(int numIterations) int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors) { -#ifdef __DREAMCAST__ - stream_ = wav_create(filePath.c_str(), 1); + Log("SoundSample::SetChunkStream({}, {}, {})", filePath, isMp3, logErrors); + + stream_ = wav_create(filePath.c_str(), filePath.find("music") != std::string::npos); + Log("stream_ = {} for {}", stream_, filePath); if (stream_ == SND_STREAM_INVALID) { if (logErrors) LogError(LogCategory::Audio, "wav_create failed (from SoundSample::SetChunkStream) for {}", filePath); @@ -175,32 +246,14 @@ int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors wav_volume(stream_, volume_); snd_stream_pan(stream_, 128, 128); return 0; -#else - SDL_RWops *handle = OpenAssetAsSdlRwOps(filePath.c_str(), /*threadsafe=*/true); - if (handle == nullptr) { - if (logErrors) - LogError(LogCategory::Audio, "OpenAsset failed (from SoundSample::SetChunkStream) for {}: {}", filePath, SDL_GetError()); - return -1; - } - file_path_ = filePath; - isMp3_ = isMp3; - stream_ = CreateStream(handle, isMp3); - if (!stream_->open()) { - stream_ = nullptr; - if (logErrors) - LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunkStream) for {}: {}", file_path_, SDL_GetError()); - return -1; - } - return 0; -#endif } -#ifdef __DREAMCAST__ int SoundSample::SetChunk(std::string filePath, std::size_t dwBytes, bool isMp3) { + // Log("SoundSample::SetChunk({}, {}, {})", filePath, dwBytes, isMp3_); isMp3_ = isMp3; file_path_ = filePath; - Log("snd_sfx_load({})", filePath); + // Log("snd_sfx_load({})", filePath); file_data_ = snd_sfx_load(filePath.c_str()); file_data_size_ = dwBytes; if (file_data_ == SFXHND_INVALID) { @@ -213,39 +266,14 @@ int SoundSample::SetChunk(std::string filePath, std::size_t dwBytes, bool isMp3) stream_ = SND_STREAM_INVALID; return 0; } -#else -int SoundSample::SetChunk(ArraySharedPtr fileData, std::size_t dwBytes, bool isMp3) -{ - isMp3_ = isMp3; - file_data_ = std::move(fileData); - file_data_size_ = dwBytes; - SDL_RWops *buf = SDL_RWFromConstMem(file_data_.get(), static_cast(dwBytes)); - if (buf == nullptr) { - return -1; - } - - stream_ = CreateStream(buf, isMp3_); - if (!stream_->open()) { - stream_ = nullptr; - file_data_ = nullptr; - LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunk): {}", SDL_GetError()); - return -1; - } - - return 0; -} -#endif void SoundSample::SetVolume(int logVolume, int logMin, int logMax) { + // Log("SoundSample::SetVolume({}, {}, {}, {}) (vol = {})", file_path_, logVolume, logMin, logMax, VolumeLogToLinear(logVolume, logMin, logMax)); if (IsStreaming()) { -#ifdef __DREAMCAST__ previousVolume_ = volume_; volume_ = VolumeLogToLinear(logVolume, logMin, logMax); wav_volume(stream_, 255); -#else - stream_->setVolume(VolumeLogToLinear(logVolume, logMin, logMax)); -#endif } else { previousVolume_ = volume_; volume_ = VolumeLogToLinear(logVolume, logMin, logMax); @@ -254,13 +282,10 @@ void SoundSample::SetVolume(int logVolume, int logMin, int logMax) void SoundSample::SetStereoPosition(int logPan) { + // Log("SoundSample::SetStereoPosition({}, {}) (stereo = {})", file_path_, logPan, PanLogToLinear(logPan)); if (IsStreaming()) { -#ifdef __DREAMCAST__ - Log("pan is not supported in libwav on the Dreamcast"); + // Log("pan is not supported in libwav on the Dreamcast"); snd_stream_pan(stream_, 128, 128); -#else - stream_->setStereoPosition(PanLogToLinear(logPan)); -#endif } else { pan_ = PanLogToLinear(logPan); } @@ -268,14 +293,10 @@ void SoundSample::SetStereoPosition(int logPan) int SoundSample::GetLength() const { -#ifdef __DREAMCAST__ + Log("SoundSample::GetLength({})", file_path_); if (IsStreaming()) return 10000; -#else - if (IsStreaming()) - return static_cast(std::chrono::duration_cast(stream_->duration()).count()); -#endif return 1000; } - +#endif } // namespace devilution diff --git a/Source/utils/soundsample.h b/Source/utils/soundsample.h index b92b3e25ce8..988985a6986 100644 --- a/Source/utils/soundsample.h +++ b/Source/utils/soundsample.h @@ -11,14 +11,116 @@ #include "utils/stdcompat/shared_ptr_array.hpp" #ifdef __DREAMCAST__ +#include "libwav.h" +#include "sndwav.h" #include #include #include -#include +#include #endif +#ifndef __DREAMCAST__ namespace devilution { +class SoundSample final { +public: + SoundSample() = default; + SoundSample(SoundSample &&) noexcept = default; + SoundSample &operator=(SoundSample &&) noexcept = default; + + [[nodiscard]] bool IsLoaded() const + { + return stream_ != nullptr; + } + + void Release(); + bool IsPlaying(); + + // Returns 0 on success. + int SetChunkStream(std::string filePath, bool isMp3, bool logErrors = true); + + void SetFinishCallback(Aulib::Stream::Callback &&callback) + { + stream_->setFinishCallback(std::forward(callback)); + } + + /** + * @brief Sets the sample's WAV, FLAC, or Ogg/Vorbis data. + * @param fileData Buffer containing the data + * @param dwBytes Length of buffer + * @param isMp3 Whether the data is an MP3 + * @return 0 on success, -1 otherwise + */ + int SetChunk(ArraySharedPtr fileData, std::size_t dwBytes, bool isMp3); + + [[nodiscard]] bool IsStreaming() const + { + return file_data_ == nullptr; + } + + int DuplicateFrom(const SoundSample &other) + { + if (other.IsStreaming()) + return SetChunkStream(other.file_path_, other.isMp3_); + return SetChunk(other.file_data_, other.file_data_size_, other.isMp3_); + } + + /** + * @brief Start playing the sound for a given number of iterations (0 means loop). + */ + bool Play(int numIterations = 1); + + /** + * @brief Start playing the sound with the given sound and user volume, and a stereo position. + */ + bool PlayWithVolumeAndPan(int logSoundVolume, int logUserVolume, int logPan) + { + SetVolume(logSoundVolume + logUserVolume * (ATTENUATION_MIN / VOLUME_MIN), ATTENUATION_MIN, 0); + SetStereoPosition(logPan); + return Play(); + } + /** + * @brief Stop playing the sound + */ + void Stop() + { + stream_->stop(); + } + + void SetVolume(int logVolume, int logMin, int logMax); + void SetStereoPosition(int logPan); + + void Mute() + { + stream_->mute(); + } + + void Unmute() + { + stream_->unmute(); + } + + /** + * @return Audio duration in ms + */ + int GetLength() const; + +private: + // Non-streaming audio fields: + ArraySharedPtr file_data_; + std::size_t file_data_size_; + + // Set for streaming audio to allow for duplicating it: + std::string file_path_; + + bool isMp3_; + + std::unique_ptr stream_; +}; +} // namespace devilution + +#else +namespace devilution { class SoundSample final { public: SoundSample() = default; @@ -27,11 +129,8 @@ class SoundSample final { [[nodiscard]] bool IsLoaded() const { -#ifdef __DREAMCAST__ + // Log("SoundSample::IsLoaded({})", file_path_); return stream_ != SND_STREAM_INVALID || file_data_ != SFXHND_INVALID; -#else - return stream_ != nullptr; -#endif } void Release(); @@ -42,11 +141,8 @@ class SoundSample final { void SetFinishCallback(Aulib::Stream::Callback &&callback) { -#ifdef __DREAMCAST__ + Log("SoundSample::SetFinishCallback({})", file_path_); Log("SetFinishCallback not implemented yet"); -#else - stream_->setFinishCallback(std::forward(callback)); -#endif } /** @@ -56,31 +152,20 @@ class SoundSample final { * @param isMp3 Whether the data is an MP3 * @return 0 on success, -1 otherwise */ -#ifdef __DREAMCAST__ int SetChunk(std::string filePath, std::size_t dwBytes, bool isMp3); -#else - int SetChunk(ArraySharedPtr fileData, std::size_t dwBytes, bool isMp3); -#endif [[nodiscard]] bool IsStreaming() const { - Log("IsStreaming {}: {}", file_path_, stream_ != SND_STREAM_INVALID); -#ifdef __DREAMCAST__ + // Log("SoundSample::IsStreaming({}) = {}", file_path_, stream_ != SND_STREAM_INVALID); return stream_ != SND_STREAM_INVALID; -#else - return file_data_ == nullptr; -#endif } int DuplicateFrom(const SoundSample &other) { + // Log("SoundSample::DuplicateFrom({}, {})", file_path_, other.file_path_); if (other.IsStreaming()) return SetChunkStream(other.file_path_, other.isMp3_); -#ifdef __DREAMCAST__ return SetChunk(other.file_path_, other.file_data_size_, other.isMp3_); -#else - return SetChunk(other.file_data_, other.file_data_size_, other.isMp3_); -#endif } /** @@ -93,6 +178,7 @@ class SoundSample final { */ bool PlayWithVolumeAndPan(int logSoundVolume, int logUserVolume, int logPan) { + // Log("SoundSample::PlayWithVolumeAndPan({}, {}, {}, {})", file_path_, logSoundVolume, logUserVolume, logPan); SetVolume(logSoundVolume + logUserVolume * (ATTENUATION_MIN / VOLUME_MIN), ATTENUATION_MIN, 0); SetStereoPosition(logPan); return Play(); @@ -103,15 +189,12 @@ class SoundSample final { */ void Stop() { + // Log("SoundSample::Stop({})", file_path_); if (IsStreaming()) { -#ifdef __DREAMCAST__ - Log("wav_stop({})", file_path_); + // Log("wav_stop({})", file_path_); wav_stop(stream_); -#else - stream_->stop(); -#endif } else { - Log("snd_sfx_stop({})", file_path_); + // Log("snd_sfx_stop({})", file_path_); snd_sfx_stop(channel_); } } @@ -121,14 +204,11 @@ class SoundSample final { void Mute() { + // Log("SoundSample::Mute({})", file_path_); if (IsStreaming()) { -#ifdef __DREAMCAST__ previousVolume_ = volume_; volume_ = 0; wav_volume(stream_, volume_); -#else - stream_->mute(); -#endif } else { previousVolume_ = volume_; volume_ = 0; @@ -137,14 +217,11 @@ class SoundSample final { void Unmute() { + // Log("SoundSample::Unmute({})", file_path_); if (IsStreaming()) { -#ifdef __DREAMCAST__ volume_ = previousVolume_; previousVolume_ = 0; wav_volume(stream_, volume_); -#else - stream_->unmute(); -#endif } else { volume_ = previousVolume_; previousVolume_ = 0; @@ -158,15 +235,11 @@ class SoundSample final { private: // Non-streaming audio fields: -#ifdef __DREAMCAST__ sfxhnd_t file_data_ = SFXHND_INVALID; int channel_ = -1; int volume_ = 0; int previousVolume_ = 0; int pan_ = 128; -#else - ArraySharedPtr file_data_; -#endif std::size_t file_data_size_; // Set for streaming audio to allow for duplicating it: @@ -174,11 +247,7 @@ class SoundSample final { bool isMp3_; -#ifdef __DREAMCAST__ wav_stream_hnd_t stream_ = SND_STREAM_INVALID; -#else - std::unique_ptr stream_; -#endif }; - } // namespace devilution +#endif From 78f2a4e915bc240bff7bef604fe818fe26bdbd8a Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Tue, 15 Apr 2025 00:54:13 +0100 Subject: [PATCH 16/18] Increase max stream counts and decrease buffer size --- .github/workflows/dreamcast.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 468e68fd658..3725e99308a 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -37,6 +37,10 @@ jobs: source /opt/toolchains/dc/kos/environ.sh && \ cd /opt/toolchains/dc/kos && \ git reset --hard dca7f6d86be234b6488bd9d7e05aef0fa10f8d96 && \ + sed -i 's/#define SND_STREAM_MAX 4/#define SND_STREAM_MAX 128/g' kernel/arch/dreamcast/include/dc/sound/stream.h && \ + echo "SND_STREAM_MAX changed to $(cat kernel/arch/dreamcast/include/dc/sound/stream.h | grep SND_STREAM_MAX)" && \ + sed -i 's/#define SND_STREAM_BUFFER_MAX_ADPCM (32 << 10)/#define SND_STREAM_BUFFER_MAX_ADPCM (8 << 10)/g' kernel/arch/dreamcast/include/dc/sound/stream.h && \ + echo "SND_STREAM_BUFFER_MAX_ADPCM changed to $(cat kernel/arch/dreamcast/include/dc/sound/stream.h | grep SND_STREAM_BUFFER_MAX_ADPCM)" && \ make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DMAKEIP_VERSION=2" - name: Build unpack_and_minify_mpq From 9cd9f1e086e562d2893a200c7a517713732a3b02 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Tue, 15 Apr 2025 01:20:12 +0100 Subject: [PATCH 17/18] Downgrade wavs to mono, 11025 hz sample rate and 4-bit Yamaha ADPCM --- .github/workflows/dreamcast.yml | 7 ++++++- Dockerfile | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 3725e99308a..52aa02447a2 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -95,10 +95,15 @@ jobs: run: | source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make -j4 + - name: Downgrade wavs to mono, 11025 hz sample rate and 4-bit Yamaha ADPCM + run: | + apk add ffmpeg && \ + cp -R spawn build/data/spawn && \ + for f in $(find spawn -name '*.wav'); do ffmpeg -y -i $f -acodec adpcm_yamaha -ac 1 -ar 11025 "build/data/$f"; done + - name: Generate .cdi run: | source /opt/toolchains/dc/kos/environ.sh && \ - mv spawn build/data/spawn && \ mv fonts/fonts/ build/data/fonts/ && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ diff --git a/Dockerfile b/Dockerfile index 50291dcecbe..24088e03f16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,11 @@ RUN echo "Patching KOS for streaming a large quantity of files" RUN source /opt/toolchains/dc/kos/environ.sh && \ cd /opt/toolchains/dc/kos && \ git reset --hard dca7f6d86be234b6488bd9d7e05aef0fa10f8d96 && \ - make clean && \ - make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DMAKEIP_VERSION=2" + sed -i 's/#define SND_STREAM_MAX 4/#define SND_STREAM_MAX 128/g' kernel/arch/dreamcast/include/dc/sound/stream.h && \ + echo "SND_STREAM_MAX changed to $(cat kernel/arch/dreamcast/include/dc/sound/stream.h | grep SND_STREAM_MAX)" && \ + sed -i 's/#define SND_STREAM_BUFFER_MAX_ADPCM (32 << 10)/#define SND_STREAM_BUFFER_MAX_ADPCM (8 << 10)/g' kernel/arch/dreamcast/include/dc/sound/stream.h && \ + echo "SND_STREAM_BUFFER_MAX_ADPCM changed to $(cat kernel/arch/dreamcast/include/dc/sound/stream.h | grep SND_STREAM_BUFFER_MAX_ADPCM)" && \ + make clean && make CFLAGS+="-DFS_CD_MAX_FILES=4096 -DFD_SETSIZE=4096 -DMAKEIP_VERSION=2" RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ @@ -57,9 +60,15 @@ RUN source /opt/toolchains/dc/kos/environ.sh && \ RUN echo "Compiling..." RUN source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make +RUN echo "Downgrading wavs to mono, 11025 hz sample rate and 4-bit Yamaha ADPCM..." +RUN source /opt/toolchains/dc/kos/environ.sh && \ + apk add ffmpeg && \ + cp -R spawn build/data/spawn && \ + for f in $(find spawn -name '*.wav'); do ffmpeg -y -i $f -acodec adpcm_yamaha -ac 1 -ar 11025 "build/data/$f"; done + RUN echo "Generating CDI" RUN source /opt/toolchains/dc/kos/environ.sh && \ - mv spawn build/data/spawn && \ + rm -rf spawn && \ mv fonts/fonts/ build/data/fonts/ && \ #mv diabdat build/data/diabdat && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ From 534897f7ef2599b6009bbdf862fd624a06329fca Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 18 Apr 2025 01:14:52 +0100 Subject: [PATCH 18/18] Apply changes to fix crash on real hardware - Add logging - Disable SDL_INIT_AUDIO flag - Disable audio in intro video - Disable sound duplication - Disable IsPlaying check in effects_play_sound, otherwise sfx only play once because snd_is_playing returns true for the sfx channel - Disable IsPlaying check in PlayEffect (monster.cpp) for the same reason --- CMake/platforms/dreamcast.cmake | 4 +- Source/diablo.cpp | 2 + Source/effects.cpp | 5 ++ Source/engine/sound.cpp | 5 ++ Source/monster.cpp | 5 ++ Source/storm/storm_svid.cpp | 16 +++++ Source/utils/display.cpp | 2 + Source/utils/soundsample.cpp | 116 ++++++++++++++++---------------- 8 files changed, 96 insertions(+), 59 deletions(-) diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index fb3b78ac723..af6a99e98f0 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -47,8 +47,10 @@ set(JOY_BUTTON_START 3) set(SDL_INCLUDE_DIR /usr/include/SDL/) set(SDL_LIBRARY /usr/lib/libSDL.a) -add_compile_options(-flto=auto) +#set(DISABLE_LTO ON) +#add_compile_options(-flto=none) #add_compile_options(-lwav) +add_compile_options(-flto=auto) # Must stream most of the audio due to RAM constraints. set(STREAM_ALL_AUDIO_MIN_FILE_SIZE 1023) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 183c94967ad..1dee5d5091e 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -108,6 +108,7 @@ #ifdef __DREAMCAST__ #include "memory_stats.h" +#include #endif namespace devilution { @@ -2548,6 +2549,7 @@ void setOnInitialized(void (*callback)()) int DiabloMain(int argc, char **argv) { #ifdef __DREAMCAST__ + // dbgio_dev_select("fb"); set_system_ram(); #endif #ifdef _DEBUG diff --git a/Source/effects.cpp b/Source/effects.cpp index 8807da12b8c..91356a5a176 100644 --- a/Source/effects.cpp +++ b/Source/effects.cpp @@ -312,7 +312,12 @@ void effects_play_sound(SfxID id) } TSFX &sfx = sgSFX[static_cast(id)]; +#ifdef __DREAMCAST__ + //todo figure out why isPlaying() always returns true after sound effect is played the first time + if (sfx.pSnd != nullptr/* && !sfx.pSnd->isPlaying()*/) { +#else if (sfx.pSnd != nullptr && !sfx.pSnd->isPlaying()) { +#endif snd_play_snd(sfx.pSnd.get(), 0, 0); } } diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index 8b6f90624a0..f11505451f3 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -128,6 +128,9 @@ std::optional duplicateSoundsMutex; SoundSample *DuplicateSound(const SoundSample &sound) { +#ifdef __DREAMCAST__ + return nullptr; +#endif auto duplicate = std::make_unique(); if (duplicate->DuplicateFrom(sound) != 0) return nullptr; @@ -194,11 +197,13 @@ void snd_play_snd(TSnd *pSnd, int lVolume, int lPan) } SoundSample *sound = &pSnd->DSB; +#ifndef __DREAMCAST__ if (sound->IsPlaying()) { sound = DuplicateSound(*sound); if (sound == nullptr) return; } +#endif sound->PlayWithVolumeAndPan(lVolume, *sgOptions.Audio.soundVolume, lPan); pSnd->start_tc = tc; diff --git a/Source/monster.cpp b/Source/monster.cpp index cb7fb62530f..3e6dd5aa0de 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -4405,7 +4405,12 @@ void PlayEffect(Monster &monster, MonsterSound mode) } TSnd *snd = monster.type().sounds[static_cast(mode)][sndIdx].get(); +#ifdef __DREAMCAST__ + //todo figure out why isPlaying() always returns true after sound effect is played the first time + if (snd == nullptr) { +#else if (snd == nullptr || snd->isPlaying()) { +#endif return; } diff --git a/Source/storm/storm_svid.cpp b/Source/storm/storm_svid.cpp index 5494d9c3290..12a388e4d80 100644 --- a/Source/storm/storm_svid.cpp +++ b/Source/storm/storm_svid.cpp @@ -8,8 +8,10 @@ #include #ifndef NOSOUND +#ifndef __DREAMCAST__ #include "utils/push_aulib_decoder.h" #endif +#endif #include "engine/assets.hpp" #include "engine/dx.h" @@ -25,11 +27,13 @@ namespace devilution { namespace { #ifndef NOSOUND +#ifndef __DREAMCAST__ std::optional SVidAudioStream; PushAulibDecoder *SVidAudioDecoder; std::uint8_t SVidAudioDepth; std::unique_ptr SVidAudioBuffer; #endif +#endif // Smacker's atomic time unit is a one hundred thousand's of a second (i.e. 0.01 millisecond, or 10 microseconds). // We use SDL ticks for timing, which have millisecond resolution. @@ -128,11 +132,13 @@ void TrySetVideoModeToSVidForSDL1() #endif #ifndef NOSOUND +#ifndef __DREAMCAST__ bool HasAudio() { return SVidAudioStream && SVidAudioStream->isPlaying(); } #endif +#endif bool SVidLoadNextFrame() { @@ -266,6 +272,7 @@ bool SVidPlayBegin(const char *filename, int flags) } #ifndef NOSOUND +#ifndef __DREAMCAST__ const bool enableAudio = (flags & 0x1000000) == 0; auto audioInfo = Smacker_GetAudioTrackDetails(SVidHandle, 0); @@ -294,6 +301,7 @@ bool SVidPlayBegin(const char *filename, int flags) SVidAudioDecoder = nullptr; } } +#endif #endif // SMK format internally defines the frame rate as the frame duration @@ -355,6 +363,7 @@ bool SVidPlayContinue() } #ifndef NOSOUND +#ifndef __DREAMCAST__ if (HasAudio()) { std::int16_t *buf = SVidAudioBuffer.get(); const auto len = Smacker_GetAudioData(SVidHandle, 0, buf); @@ -364,6 +373,7 @@ bool SVidPlayContinue() SVidAudioDecoder->PushSamples(reinterpret_cast(buf), len); } } +#endif #endif if (GetTicksSmk() >= SVidFrameEnd) { @@ -384,11 +394,13 @@ bool SVidPlayContinue() void SVidPlayEnd() { #ifndef NOSOUND +#ifndef __DREAMCAST__ if (HasAudio()) { SVidAudioStream = std::nullopt; SVidAudioDecoder = nullptr; SVidAudioBuffer = nullptr; } +#endif #endif if (SVidHandle.isValid) @@ -416,17 +428,21 @@ void SVidPlayEnd() void SVidMute() { #ifndef NOSOUND +#ifndef __DREAMCAST__ if (SVidAudioStream) SVidAudioStream->mute(); #endif +#endif } void SVidUnmute() { #ifndef NOSOUND +#ifndef __DREAMCAST__ if (SVidAudioStream) SVidAudioStream->unmute(); #endif +#endif } } // namespace devilution diff --git a/Source/utils/display.cpp b/Source/utils/display.cpp index 67e366dc269..0d82bfae986 100644 --- a/Source/utils/display.cpp +++ b/Source/utils/display.cpp @@ -314,8 +314,10 @@ bool SpawnWindow(const char *lpWindowName) int initFlags = SDL_INIT_VIDEO | SDL_INIT_JOYSTICK; #ifndef NOSOUND +#ifndef __DREAMCAST__ initFlags |= SDL_INIT_AUDIO; #endif +#endif #ifndef USE_SDL1 initFlags |= SDL_INIT_GAMECONTROLLER; diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index b161fd56873..30c51e1f5ec 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -178,18 +178,18 @@ int SoundSample::GetLength() const void SoundSample::Release() { - // Log("SoundSample::Release({})", file_path_); - if (stream_ != SND_STREAM_INVALID) { - wav_destroy(stream_); - stream_ = SND_STREAM_INVALID; - } - if (file_data_ != SFXHND_INVALID) { - snd_sfx_unload(file_data_); - file_data_ = SFXHND_INVALID; - channel_ = -1; - } - volume_ = 0; - previousVolume_ = 0; + // Log("SoundSample::Release({})", file_path_); + if(stream_ != SND_STREAM_INVALID) { + wav_destroy(stream_); + stream_ = SND_STREAM_INVALID; + } + if(file_data_ != SFXHND_INVALID) { + snd_sfx_unload(file_data_); + file_data_ = SFXHND_INVALID; + channel_ = -1; + } + volume_ = 0; + previousVolume_ = 0; file_data_size_ = 0; } @@ -199,40 +199,40 @@ void SoundSample::Release() bool SoundSample::IsPlaying() { // Log("SoundSample::IsPlaying({})", file_path_); - if (IsStreaming()) { + if(IsStreaming()) { // Log("wav_is_playing({}) = {}", file_path_, wav_is_playing(stream_)); return wav_is_playing(stream_); - } else { - return snd_is_playing(channel_); } + //todo figure out why snd_is_playing(channel_) always returns true after sound effect is played the first time + // Log("snd_is_playing({}, {}) = {}", channel_, file_data_, file_data_ != SFXHND_INVALID && channel_ != -1 && snd_is_playing(channel_)); + return file_data_ != SFXHND_INVALID && channel_ != -1 && snd_is_playing(channel_); } bool SoundSample::Play(int numIterations) { - // Log("SoundSample::Play({}, {})", file_path_, numIterations); - if (IsStreaming()) { - Log("Streaming {} with audio 255", file_path_); - wav_volume(stream_, 255); - wav_play(stream_); - return true; - } - volume_ = 255; - // Log("snd_sfx_play({}, {}, {})", file_path_, volume_, pan_); - int channel = snd_sfx_play(file_data_, volume_, pan_); - if (channel == -1) { + // Log("SoundSample::Play({}, {})", file_path_, numIterations); + if(IsStreaming()) { + // Log("Streaming {} with audio 255", file_path_); + wav_volume(stream_, 255); + wav_play(stream_); + return true; + } + volume_ = 255; + // Log("snd_sfx_play({}, {}, {})", file_path_, volume_, pan_); + channel_ = snd_sfx_play(file_data_, volume_, pan_); + if(channel_ == -1) { LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); - return false; - } - channel_ = channel; - return true; + return false; + } + return true; } int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors) { - Log("SoundSample::SetChunkStream({}, {}, {})", filePath, isMp3, logErrors); + // Log("SoundSample::SetChunkStream({}, {}, {})", filePath, isMp3, logErrors); stream_ = wav_create(filePath.c_str(), filePath.find("music") != std::string::npos); - Log("stream_ = {} for {}", stream_, filePath); + // Log("stream_ = {} for {}", stream_, filePath); if (stream_ == SND_STREAM_INVALID) { if (logErrors) LogError(LogCategory::Audio, "wav_create failed (from SoundSample::SetChunkStream) for {}", filePath); @@ -240,17 +240,17 @@ int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors } file_path_ = filePath; isMp3_ = isMp3; - file_data_ = SFXHND_INVALID; - volume_ = 255; - pan_ = 128; - wav_volume(stream_, volume_); + file_data_ = SFXHND_INVALID; + volume_ = 255; + pan_ = 128; + wav_volume(stream_, volume_); snd_stream_pan(stream_, 128, 128); - return 0; + return 0; } int SoundSample::SetChunk(std::string filePath, std::size_t dwBytes, bool isMp3) { - // Log("SoundSample::SetChunk({}, {}, {})", filePath, dwBytes, isMp3_); + // Log("SoundSample::SetChunk({}, {}, {})", filePath, dwBytes, isMp3_); isMp3_ = isMp3; file_path_ = filePath; // Log("snd_sfx_load({})", filePath); @@ -261,39 +261,39 @@ int SoundSample::SetChunk(std::string filePath, std::size_t dwBytes, bool isMp3) return -1; } - volume_ = 255; - pan_ = 128; - stream_ = SND_STREAM_INVALID; + volume_ = 255; + pan_ = 128; + stream_ = SND_STREAM_INVALID; return 0; } void SoundSample::SetVolume(int logVolume, int logMin, int logMax) { - // Log("SoundSample::SetVolume({}, {}, {}, {}) (vol = {})", file_path_, logVolume, logMin, logMax, VolumeLogToLinear(logVolume, logMin, logMax)); - if (IsStreaming()) { - previousVolume_ = volume_; - volume_ = VolumeLogToLinear(logVolume, logMin, logMax); - wav_volume(stream_, 255); - } else { - previousVolume_ = volume_; - volume_ = VolumeLogToLinear(logVolume, logMin, logMax); - } + // Log("SoundSample::SetVolume({}, {}, {}, {}) (vol = {})", file_path_, logVolume, logMin, logMax, VolumeLogToLinear(logVolume, logMin, logMax)); + if(IsStreaming()) { + previousVolume_ = volume_; + volume_ = VolumeLogToLinear(logVolume, logMin, logMax); + wav_volume(stream_, 255); + } else { + previousVolume_ = volume_; + volume_ = VolumeLogToLinear(logVolume, logMin, logMax); + } } void SoundSample::SetStereoPosition(int logPan) { - // Log("SoundSample::SetStereoPosition({}, {}) (stereo = {})", file_path_, logPan, PanLogToLinear(logPan)); - if (IsStreaming()) { - // Log("pan is not supported in libwav on the Dreamcast"); - snd_stream_pan(stream_, 128, 128); - } else { - pan_ = PanLogToLinear(logPan); - } + // Log("SoundSample::SetStereoPosition({}, {}) (stereo = {})", file_path_, logPan, PanLogToLinear(logPan)); + if(IsStreaming()) { + //Log("pan is not supported in libwav on the Dreamcast"); + snd_stream_pan(stream_, 128, 128); + } else { + pan_ = PanLogToLinear(logPan); + } } int SoundSample::GetLength() const { - Log("SoundSample::GetLength({})", file_path_); + //Log("SoundSample::GetLength({})", file_path_); if (IsStreaming()) return 10000; return 1000;