BCSTM Player
This commit is contained in:
2
Makefile
2
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
272
source/music.c
272
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("<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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user