diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 3c929007ed4..52aa02447a2 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -25,13 +25,24 @@ concurrency: jobs: build: runs-on: ubuntu-latest - container: azihassan/kallistios:docker + container: azihassan/kallistios:1b9c7b6bc4858d947a12675c58122951dd42c4db steps: - name: Checkout uses: actions/checkout@v4 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 && \ + 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 run: | git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ @@ -82,12 +93,17 @@ 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: 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/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() diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 3161c23f131..af6a99e98f0 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,16 @@ set(JOY_BUTTON_START 3) set(SDL_INCLUDE_DIR /usr/include/SDL/) set(SDL_LIBRARY /usr/lib/libSDL.a) -add_compile_options(-flto=none) +#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) + +# 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/CMakeLists.txt b/CMakeLists.txt index 52ec92a517f..c3d93966af9 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/Dockerfile b/Dockerfile index 6c327a9fa6f..24088e03f16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,14 @@ -FROM azihassan/kallistios:fdffe33635239d46bcccf0d5c4d59bb7d2d91f38 +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 && \ + 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" RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ @@ -50,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/ diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index f291841044c..57033c39dd8 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -281,6 +281,13 @@ 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 + libwav.c + sndwav.c + ) +endif() add_devilutionx_library(libdevilutionx OBJECT ${libdevilutionx_SRCS}) target_include_directories(libdevilutionx PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index d8974ce86fc..1dee5d5091e 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -106,6 +106,11 @@ #include #endif +#ifdef __DREAMCAST__ +#include "memory_stats.h" +#include +#endif + namespace devilution { uint32_t DungeonSeeds[NUMLEVELS]; @@ -2543,6 +2548,10 @@ void setOnInitialized(void (*callback)()) int DiabloMain(int argc, char **argv) { +#ifdef __DREAMCAST__ + // dbgio_dev_select("fb"); + set_system_ram(); +#endif #ifdef _DEBUG SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG); #endif diff --git a/Source/effects.cpp b/Source/effects.cpp index 1df897a0916..91356a5a176 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); @@ -304,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 b9125881849..f11505451f3 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 22 * 1024 + namespace devilution { bool gbSndInited; @@ -71,8 +76,17 @@ 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) { +#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__); } @@ -88,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) @@ -95,6 +112,8 @@ 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) ErrSdl(); @@ -109,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; @@ -175,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; @@ -211,6 +235,18 @@ void snd_init() sgOptions.Audio.musicVolume.SetValue(CapVolume(*sgOptions.Audio.musicVolume)); gbMusicOn = *sgOptions.Audio.musicVolume > VOLUME_MIN; +#ifdef __DREAMCAST__ + printf("snd_init() in sound.cpp\n"); + gbMusicOn = true; + // spu_init(); + ::snd_init(); + snd_stream_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 // (stereo), and a 2KiB output buffer. @@ -221,6 +257,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; } @@ -228,7 +265,14 @@ void snd_init() void snd_deinit() { if (gbSndInited) { + printf("snd_deinit() in sound.cpp\n"); +#ifdef __DREAMCAST__ + wav_shutdown(); + snd_stream_shutdown(); + ::snd_shutdown(); +#else Aulib::quit(); +#endif duplicateSoundsMutex = std::nullopt; } @@ -259,12 +303,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); @@ -276,12 +322,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; } @@ -295,7 +344,9 @@ void music_start(_music_id nTrack) return; } + Log(" music.Play()"); sgnMusicTrack = nTrack; + Log(" sgnMusicTrack = {}", (int)sgnMusicTrack); } void sound_disable_music(bool disable) @@ -332,12 +383,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 6ca09631499..2905d3f9151 100644 --- a/Source/engine/sound.h +++ b/Source/engine/sound.h @@ -13,6 +13,13 @@ #include "utils/attributes.h" #ifndef NOSOUND +#ifdef __DREAMCAST__ +#include "memory_stats.h" +#include "sndwav.h" +#include +#include +#endif + #include "utils/soundsample.h" #endif 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/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 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/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/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 ac34d536969..30c51e1f5ec 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -91,6 +91,7 @@ float VolumeLogToLinear(int logVolume, int logMin, int logMax) ///// SoundSample ///// +#ifndef __DREAMCAST__ void SoundSample::Release() { stream_ = nullptr; @@ -173,4 +174,129 @@ int SoundSample::GetLength() const 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; + } + if(file_data_ != SFXHND_INVALID) { + snd_sfx_unload(file_data_); + file_data_ = SFXHND_INVALID; + channel_ = -1; + } + volume_ = 0; + previousVolume_ = 0; + file_data_size_ = 0; +} + +/** + * @brief Check if a the sound is being played atm + */ +bool SoundSample::IsPlaying() +{ + // Log("SoundSample::IsPlaying({})", file_path_); + if(IsStreaming()) { + // Log("wav_is_playing({}) = {}", file_path_, wav_is_playing(stream_)); + return wav_is_playing(stream_); + } + //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_); + channel_ = snd_sfx_play(file_data_, volume_, pan_); + if(channel_ == -1) { + 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) +{ + // 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); + 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; +} + +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); + 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; +} + +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); + } +} + +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); + } +} + +int SoundSample::GetLength() const +{ + //Log("SoundSample::GetLength({})", file_path_); + if (IsStreaming()) + return 10000; + return 1000; +} +#endif } // namespace devilution diff --git a/Source/utils/soundsample.h b/Source/utils/soundsample.h index 5673d6479cd..988985a6986 100644 --- a/Source/utils/soundsample.h +++ b/Source/utils/soundsample.h @@ -7,10 +7,20 @@ #include #include "engine/sound_defs.hpp" +#include "utils/log.hpp" #include "utils/stdcompat/shared_ptr_array.hpp" -namespace devilution { +#ifdef __DREAMCAST__ +#include "libwav.h" +#include "sndwav.h" +#include +#include +#include +#include +#endif +#ifndef __DREAMCAST__ +namespace devilution { class SoundSample final { public: SoundSample() = default; @@ -107,5 +117,137 @@ class SoundSample final { std::unique_ptr stream_; }; +} // namespace devilution + +#else +namespace devilution { +class SoundSample final { +public: + SoundSample() = default; + SoundSample(SoundSample &&) noexcept = default; + SoundSample &operator=(SoundSample &&) noexcept = default; + + [[nodiscard]] bool IsLoaded() const + { + // Log("SoundSample::IsLoaded({})", file_path_); + return stream_ != SND_STREAM_INVALID || file_data_ != SFXHND_INVALID; + } + + void Release(); + bool IsPlaying(); + + // Returns 0 on success. + int SetChunkStream(std::string filePath, bool isMp3, bool logErrors = true); + + void SetFinishCallback(Aulib::Stream::Callback &&callback) + { + Log("SoundSample::SetFinishCallback({})", file_path_); + Log("SetFinishCallback not implemented yet"); + } + + /** + * @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(std::string filePath, std::size_t dwBytes, bool isMp3); + + [[nodiscard]] bool IsStreaming() const + { + // Log("SoundSample::IsStreaming({}) = {}", file_path_, stream_ != SND_STREAM_INVALID); + return stream_ != SND_STREAM_INVALID; + } + + int DuplicateFrom(const SoundSample &other) + { + // Log("SoundSample::DuplicateFrom({}, {})", file_path_, other.file_path_); + if (other.IsStreaming()) + return SetChunkStream(other.file_path_, other.isMp3_); + return SetChunk(other.file_path_, 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) + { + // Log("SoundSample::PlayWithVolumeAndPan({}, {}, {}, {})", file_path_, logSoundVolume, logUserVolume, logPan); + SetVolume(logSoundVolume + logUserVolume * (ATTENUATION_MIN / VOLUME_MIN), ATTENUATION_MIN, 0); + SetStereoPosition(logPan); + return Play(); + } + /** + * @brief Stop playing the sound + */ + void Stop() + { + // Log("SoundSample::Stop({})", file_path_); + if (IsStreaming()) { + // Log("wav_stop({})", file_path_); + wav_stop(stream_); + } else { + // Log("snd_sfx_stop({})", file_path_); + snd_sfx_stop(channel_); + } + } + + void SetVolume(int logVolume, int logMin, int logMax); + void SetStereoPosition(int logPan); + + void Mute() + { + // Log("SoundSample::Mute({})", file_path_); + if (IsStreaming()) { + previousVolume_ = volume_; + volume_ = 0; + wav_volume(stream_, volume_); + } else { + previousVolume_ = volume_; + volume_ = 0; + } + } + + void Unmute() + { + // Log("SoundSample::Unmute({})", file_path_); + if (IsStreaming()) { + volume_ = previousVolume_; + previousVolume_ = 0; + wav_volume(stream_, volume_); + } else { + volume_ = previousVolume_; + previousVolume_ = 0; + } + } + + /** + * @return Audio duration in ms + */ + int GetLength() const; + +private: + // Non-streaming audio fields: + sfxhnd_t file_data_ = SFXHND_INVALID; + int channel_ = -1; + int volume_ = 0; + int previousVolume_ = 0; + int pan_ = 128; + std::size_t file_data_size_; + + // Set for streaming audio to allow for duplicating it: + std::string file_path_; + + bool isMp3_; + + wav_stream_hnd_t stream_ = SND_STREAM_INVALID; +}; } // namespace devilution +#endif