From e09c33f0e8ad75eb86b3029c9e133f218fdd4e23 Mon Sep 17 00:00:00 2001 From: Alex Taber Date: Tue, 4 Jun 2024 17:17:16 -0400 Subject: [PATCH] BCSTM Player --- Makefile | 2 +- include/loading.h | 1 + include/music.h | 37 ++++++- source/loading.c | 18 ++- source/music.c | 272 +++++++++++++++++++++++++++++++++++++++++++++- source/remote.c | 16 +-- 6 files changed, 329 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 2a86818..a278652 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ CFLAGS := -g -Wall -Wextra -O2 -mword-relocations \ $(ARCH) CFLAGS += $(INCLUDE) -D__3DS__ -D_GNU_SOURCE -DVERSION="\"$(VERSION)\"" -DUSER_AGENT="\"$(APP_TITLE)/$(VERSION)\"" -DAPP_TITLE="\"$(APP_TITLE)\"" -CFLAGS += `arm-none-eabi-pkg-config --cflags-only-other vorbisidec libarchive jansson libpng` +CFLAGS += `arm-none-eabi-pkg-config --cflags-only-other libcurl vorbisidec libarchive jansson libpng` ifneq ($(strip $(CITRA_MODE)),) CFLAGS += -DCITRA_MODE endif diff --git a/include/loading.h b/include/loading.h index 9363608..ea0513f 100644 --- a/include/loading.h +++ b/include/loading.h @@ -72,6 +72,7 @@ bool load_preview_from_buffer(char * row_pointers, u32 size, C2D_Image * preview bool load_preview(const Entry_List_s * list, C2D_Image * preview_image, int * preview_offset); void free_preview(C2D_Image preview_image); Result load_audio(const Entry_s *, audio_s *); +Result load_audio_ogg(const Entry_s * entry, audio_ogg_s * audio); void load_icons_first(Entry_List_s * current_list, bool silent); void handle_scrolling(Entry_List_s * list); void load_icons_thread(void * void_arg); diff --git a/include/music.h b/include/music.h index 4a71914..fb9a7a2 100644 --- a/include/music.h +++ b/include/music.h @@ -34,7 +34,37 @@ #include #include -#define BUF_TO_READ 48000 // How much data should be buffered at a time +#define BUFFER_COUNT 2 +#define BUF_TO_READ 48000 // How much data should be buffered at a time for ogg + +typedef struct { + char *music_buf; + ssize_t music_size; + ssize_t cursor; + u32 last_time; + bool is_little_endian; + bool is_looping; + u32 info_offset; + u32 data_offset; + u8 channel_count; + u32 sample_rate; + u32 loop_start; + u32 loop_end; + u32 num_blocks; + u32 block_size; + u32 block_samples; + u32 last_block_samples; + u32 last_block_size; + u32 current_block; + unsigned short adpcm_coefs[2][16]; + ndspWaveBuf wave_buf[2][BUFFER_COUNT]; + ndspAdpcmData adpcm_data[2][2]; + unsigned short channel[2]; + unsigned int active_channels; + u8 *buffer_data[2][BUFFER_COUNT]; + volatile bool stop; + Thread playing_thread; +} audio_s; typedef struct { OggVorbis_File vf; @@ -47,9 +77,12 @@ typedef struct { volatile bool stop; Thread playing_thread; -} audio_s; +} audio_ogg_s; void play_audio(audio_s *); void stop_audio(audio_s **); +void play_audio_ogg(audio_ogg_s *); +void stop_audio_ogg(audio_ogg_s **); + #endif diff --git a/source/loading.c b/source/loading.c index 2a81627..9b1c5aa 100644 --- a/source/loading.c +++ b/source/loading.c @@ -441,6 +441,22 @@ void free_preview(C2D_Image preview) // Initialize the audio struct Result load_audio(const Entry_s * entry, audio_s * audio) +{ + audio->music_size = load_data("/bgm.bcstm", entry, &audio->music_buf); + if (audio->music_size == 0) { + free(audio); + DEBUG(" File not found!\n"); + return MAKERESULT(RL_FATAL, RS_NOTFOUND, RM_APPLICATION, RD_NOT_FOUND); + } + audio->cursor = 0; + audio->last_time = 0; + audio->current_block = 0; + audio->stop = false; + audio->active_channels = 0; + return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS); +} + +Result load_audio_ogg(const Entry_s * entry, audio_ogg_s * audio) { audio->filesize = load_data("/bgm.ogg", entry, &audio->filebuf); if (audio->filesize == 0) { @@ -493,4 +509,4 @@ Result load_audio(const Entry_s * entry, audio_s * audio) DEBUG(" fmemopen failed!\n"); return MAKERESULT(RL_FATAL, RS_NOTFOUND, RM_APPLICATION, RD_NOT_FOUND); } -} +} \ No newline at end of file diff --git a/source/music.c b/source/music.c index 768d162..ef01a91 100644 --- a/source/music.c +++ b/source/music.c @@ -27,8 +27,31 @@ #include "music.h" #include "loading.h" -// Play a given audio struct -Result update_audio(audio_s * audio) +u32 read32(char *music_buf, ssize_t *cursor) +{ + u32 ret; + memcpy(&ret, music_buf + *cursor, 4); + *cursor += 4; + return ret; +} + +u16 read16(char *music_buf, ssize_t *cursor) +{ + u16 ret; + memcpy(&ret, music_buf + *cursor, 2); + *cursor += 2; + return ret; +} + +u8 read8(char *music_buf, ssize_t *cursor) +{ + u8 ret; + memcpy(&ret, music_buf + *cursor, 1); + *cursor += 1; + return ret; +} + +Result update_audio_ogg(audio_ogg_s * audio) { u32 size = audio->wave_buf[audio->buf_pos].nsamples * 4 - audio->data_read; DEBUG(" Audio Size: %ld\n", size); @@ -64,10 +87,10 @@ Result update_audio(audio_s * audio) return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS); } -void thread_audio(void * data) { - audio_s * audio = (audio_s *)data; +void thread_audio_ogg(void * data) { + audio_ogg_s * audio = (audio_ogg_s *)data; while(!audio->stop) { - update_audio(audio); + update_audio_ogg(audio); } ndspChnWaveBufClear(0); ndspChnReset(0); @@ -77,6 +100,245 @@ void thread_audio(void * data) { linearFree((void *)audio->wave_buf[1].data_vaddr); } +void play_audio_ogg(audio_ogg_s * audio) { + audio->playing_thread = threadCreate(thread_audio_ogg, audio, 0x1000, 0x3F, 1, false); +} + +void stop_audio_ogg(audio_ogg_s ** audio_ptr) { + audio_ogg_s * audio = *audio_ptr; + if(audio->playing_thread) + { + audio->stop = true; + threadJoin(audio->playing_thread, U64_MAX); + threadFree(audio->playing_thread); + } + free(audio); + *audio_ptr = NULL; +} + +int init_audio(audio_s *audio) +{ + u32 magic = read32(audio->music_buf, &audio->cursor); + DEBUG("Loading music, music_size: %lu, magic: 0x%08lx\n", audio->music_size, magic); + audio->is_little_endian = read16(audio->music_buf, &audio->cursor) == 0xFEFF; + audio->info_offset = 0; + audio->data_offset = 0; + + if (magic != 0x4D545343) // CSTM + { + free(audio->music_buf); + return -1; + } + + audio->cursor = 0x10; + u16 sbc = read16(audio->music_buf, &audio->cursor); + audio->cursor += 2; + + for (u16 i = 0; i < sbc; ++i) + { + u16 sec = read16(audio->music_buf, &audio->cursor); + audio->cursor += 2; + u32 off = read32(audio->music_buf, &audio->cursor); + audio->cursor += 4; + DEBUG("Reading sbc: %04lx, %08lx\n", sec, off); + if (sec == 0x4000) // Info block + audio->info_offset = off; + if (sec == 0x4002) // Data block + audio->data_offset = off; + } + + DEBUG("Info offset: 0x%08lx, data offset: 0x%08lx\n", audio->info_offset, audio->data_offset); + + if (audio->data_offset == 0 || audio->info_offset == 0) + { + free(audio->music_buf); + return -2; + } + + audio->cursor = audio->info_offset + 0x20; + + if (read8(audio->music_buf, &audio->cursor) != 2) // Encoding - 2 is DSP_ADPCM + { + free(audio->music_buf); + return -3; + } + + audio->is_looping = read8(audio->music_buf, &audio->cursor); + audio->channel_count = read8(audio->music_buf, &audio->cursor); + + if (audio->channel_count > 2) + { + free(audio->music_buf); + return -4; + } + + DEBUG("Channel count: %d, is looping: %d\n", audio->channel_count, audio->is_looping); + + audio->cursor = audio->info_offset + 0x24; + audio->sample_rate = read32(audio->music_buf, &audio->cursor); + u32 _loop_pos = read32(audio->music_buf, &audio->cursor); + u32 _loop_end = read32(audio->music_buf, &audio->cursor); + audio->num_blocks = read32(audio->music_buf, &audio->cursor); + audio->block_size = read32(audio->music_buf, &audio->cursor); + audio->block_samples = read32(audio->music_buf, &audio->cursor); + audio->cursor += 4; + audio->last_block_samples = read32(audio->music_buf, &audio->cursor); + audio->last_block_size = read32(audio->music_buf, &audio->cursor); + + DEBUG("sample_rate: %lu, loop_start: %lu, loop_end: %lu, num_blocks: %lu, block_size: %lu, block_samples: %lu, last_block_samples: %lu, last_block_size: %lu\n", + audio->sample_rate, _loop_pos, _loop_end, audio->num_blocks, audio->block_size, audio->block_samples, audio->last_block_samples, audio->last_block_size); + + audio->loop_start = _loop_pos / audio->block_samples; + audio->loop_end = (_loop_end % audio->block_samples ? audio->num_blocks : _loop_end / audio->block_samples); + + while (read32(audio->music_buf, &audio->cursor) != 0x4102); // find channel info header + audio->cursor += read32(audio->music_buf, &audio->cursor) + audio->channel_count * 8 - 12; + + for (u8 i = 0; i < audio->channel_count; ++i) + { + memcpy(audio->adpcm_coefs[i], audio->music_buf + audio->cursor, sizeof(unsigned short) * 16); + audio->cursor += sizeof(unsigned short) * 16; + memcpy(&(audio->adpcm_data[i][0]), audio->music_buf + audio->cursor, sizeof(ndspAdpcmData)); + audio->cursor += sizeof(ndspAdpcmData); + memcpy(&(audio->adpcm_data[i][1]), audio->music_buf + audio->cursor, sizeof(ndspAdpcmData)); + audio->cursor += sizeof(ndspAdpcmData); + audio->cursor += 2; // skip padding + } + + audio->cursor = audio->data_offset + 0x20; + + return 0; +} + +int start_play(audio_s *audio) { + audio->current_block = 0; + for (u8 i = 0; i < audio->channel_count; ++i) + { + audio->channel[i] = 0; + while (audio->channel[i] < 24 && ((audio->active_channels >> audio->channel[i]) & 1)) { + audio->channel[i]++; + } + if (audio->channel[i] == 24) { + return -1; + } + audio->active_channels |= 1 << audio->channel[i]; + ndspChnWaveBufClear(audio->channel[i]); + + static float mix[16]; + ndspChnSetFormat(audio->channel[i], NDSP_FORMAT_ADPCM | NDSP_3D_SURROUND_PREPROCESSED); + ndspChnSetRate(audio->channel[i], audio->sample_rate); + + if (audio->channel_count == 1) + { + mix[0] = mix[1] = 0.5f; + } else if (audio->channel_count == 2) + { + if (i == 0) + { + mix[0] = 0.8f; + mix[1] = 0.0f; + mix[2] = 0.2f; + mix[3] = 0.0f; + } else + { + mix[0] = 0.0f; + mix[1] = 0.8f; + mix[2] = 0.0f; + mix[3] = 0.2f; + } + } + ndspChnSetMix(audio->channel[i], mix); + ndspChnSetAdpcmCoefs(audio->channel[i], audio->adpcm_coefs[i]); + + for (u8 j = 0; j < BUFFER_COUNT; ++j) + { + memset(&(audio->wave_buf[i][j]), 0, sizeof(ndspWaveBuf)); + audio->wave_buf[i][j].status = NDSP_WBUF_DONE; + audio->buffer_data[i][j] = linearAlloc(audio->block_size); + } + } + + return 0; +} + +void fill_buffers(audio_s *audio) +{ + DEBUG("Filling buffers...\n"); + for (u8 bufIndex = 0; bufIndex < BUFFER_COUNT; ++bufIndex) + { + if (audio->wave_buf[0][bufIndex].status != NDSP_WBUF_DONE) continue; + if (audio->channel_count == 2 && audio->wave_buf[1][bufIndex].status != NDSP_WBUF_DONE) continue; + + if (audio->is_looping && audio->current_block == audio->loop_end) + { + audio->current_block = audio->loop_start; + audio->cursor = audio->data_offset + 0x20 + audio->block_size * audio->channel_count * audio->loop_start; + } + + if (!audio->is_looping && audio->current_block == audio->loop_end) + { + // stop playing + } + + for (u8 channelIndex = 0; channelIndex < audio->channel_count; ++channelIndex) + { + ndspWaveBuf *buf = &(audio->wave_buf[channelIndex][bufIndex]); + memset(buf, 0, sizeof(ndspWaveBuf)); + buf->data_adpcm = audio->buffer_data[channelIndex][bufIndex]; + memcpy(buf->data_adpcm, audio->music_buf + audio->cursor, (audio->current_block == audio->num_blocks - 1) ? audio->last_block_size : audio->block_size); + audio->cursor += (audio->current_block == audio->num_blocks - 1) ? audio->last_block_size : audio->block_size; + DSP_FlushDataCache(buf->data_adpcm, audio->block_size); + + if (audio->current_block == 0) + buf->adpcm_data = &(audio->adpcm_data[channelIndex][0]); + else if (audio->current_block == audio->loop_start) + buf->adpcm_data = &(audio->adpcm_data[channelIndex][1]); + + if (audio->current_block == audio->num_blocks - 1) + buf->nsamples = audio->last_block_samples; + else + buf->nsamples = audio->block_samples; + + ndspChnWaveBufAdd(audio->channel[channelIndex], buf); + } + audio->current_block++; + } +} + +// Play a given audio struct +void update_audio(audio_s * audio) +{ + u32 current_time = svcGetSystemTick(); + if (current_time - audio->last_time > 1e8) + { + fill_buffers(audio); + audio->last_time = current_time; + } +} + +void thread_audio(void * data) { + audio_s * audio = (audio_s *)data; + int res = init_audio(audio); + if (res < 0) + { + return; + } + start_play(audio); + while(!audio->stop) { + update_audio(audio); + } + for (u8 i = 0; i < audio->channel_count; ++i) + { + ndspChnWaveBufClear(audio->channel[i]); + ndspChnReset(audio->channel[i]); + audio->active_channels &= ~(1 << audio->channel[i]); + for (u8 j = 0; j < BUFFER_COUNT; ++j) { + linearFree(audio->buffer_data[i][j]); + } + } + free(audio->music_buf); +} + void play_audio(audio_s * audio) { audio->playing_thread = threadCreate(thread_audio, audio, 0x1000, 0x3F, 1, false); } diff --git a/source/remote.c b/source/remote.c index 0748019..ea7c673 100644 --- a/source/remote.c +++ b/source/remote.c @@ -475,7 +475,7 @@ bool themeplaza_browser(RemoteMode mode) bool preview_mode = false; int preview_offset = 0; - audio_s * audio = NULL; + audio_ogg_s * audio = NULL; Entry_List_s list = { 0 }; Entry_List_s * current_list = &list; @@ -608,9 +608,9 @@ bool themeplaza_browser(RemoteMode mode) if (mode == REMOTE_MODE_THEMES && dspfirm) { load_remote_bgm(current_entry); - audio = calloc(1, sizeof(audio_s)); - if (R_FAILED(load_audio(current_entry, audio))) audio = NULL; - if (audio != NULL) play_audio(audio); + audio = calloc(1, sizeof(audio_ogg_s)); + if (R_FAILED(load_audio_ogg(current_entry, audio))) audio = NULL; + if (audio != NULL) play_audio_ogg(audio); } } else @@ -618,7 +618,7 @@ bool themeplaza_browser(RemoteMode mode) preview_mode = false; if (mode == REMOTE_MODE_THEMES && audio != NULL) { - stop_audio(&audio); + stop_audio_ogg(&audio); } } continue; @@ -630,7 +630,7 @@ bool themeplaza_browser(RemoteMode mode) preview_mode = false; if (mode == REMOTE_MODE_THEMES && audio != NULL) { - stop_audio(&audio); + stop_audio_ogg(&audio); } } else @@ -696,7 +696,7 @@ bool themeplaza_browser(RemoteMode mode) preview_mode = false; if (mode == REMOTE_MODE_THEMES && audio) { - stop_audio(&audio); + stop_audio_ogg(&audio); } continue; } @@ -761,7 +761,7 @@ bool themeplaza_browser(RemoteMode mode) if (audio) { - stop_audio(&audio); + stop_audio_ogg(&audio); } free_preview(preview);