BCSTM Player

This commit is contained in:
2024-06-04 17:17:16 -04:00
parent 8f676eafae
commit e09c33f0e8
6 changed files with 329 additions and 17 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -34,7 +34,37 @@
#include <tremor/ivorbisfile.h>
#include <tremor/ivorbiscodec.h>
#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

View File

@@ -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("<load_audio> 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("<load_audio> fmemopen failed!\n");
return MAKERESULT(RL_FATAL, RS_NOTFOUND, RM_APPLICATION, RD_NOT_FOUND);
}
}
}

View File

@@ -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("<update_audio> 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);
}

View File

@@ -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);