diff --git a/engine/external/jar/jar_mod.h b/engine/external/jar/jar_mod.h index 791939c..7cbfa22 100644 --- a/engine/external/jar/jar_mod.h +++ b/engine/external/jar/jar_mod.h @@ -254,6 +254,7 @@ gf_bool_t jar_mod_setcfg(jar_mod_context_t* modctx, int samplerate, int bits, in void jar_mod_fillbuffer(jar_mod_context_t* modctx, gf_int16_t* outbuffer, unsigned long nbsample, jar_mod_tracker_buffer_state* trkbuf); void jar_mod_unload(jar_mod_context_t* modctx); mulong jar_mod_load_file(jar_mod_context_t* modctx, const char* filename); +gf_bool_t jar_mod_load(jar_mod_context_t* modctx, void* mod_data, int mod_data_size); mulong jar_mod_current_samples(jar_mod_context_t* modctx); mulong jar_mod_max_samples(jar_mod_context_t* modctx); void jar_mod_seek_start(jar_mod_context_t* ctx); @@ -969,7 +970,7 @@ gf_bool_t jar_mod_setcfg(jar_mod_context_t* modctx, int samplerate, int bits, in } /* make certain that mod_data stays in memory while playing */ -static gf_bool_t jar_mod_load(jar_mod_context_t* modctx, void* mod_data, int mod_data_size) { +gf_bool_t jar_mod_load(jar_mod_context_t* modctx, void* mod_data, int mod_data_size) { muint i, max; unsigned short t; sample* sptr; @@ -1090,7 +1091,7 @@ void jar_mod_fillbuffer(jar_mod_context_t* modctx, gf_int16_t* outbuffer, unsign note* nptr; channel* cptr; - if(modctx && outbuffer) { + if(modctx) { if(modctx->mod_loaded) { state_remaining_steps = 0; @@ -1247,8 +1248,10 @@ void jar_mod_fillbuffer(jar_mod_context_t* modctx, gf_int16_t* outbuffer, unsign if(r < -32768) r = -32768; /* Store the final sample. */ - outbuffer[(i * 2)] = l; - outbuffer[(i * 2) + 1] = r; + if(outbuffer != NULL) { + outbuffer[(i * 2)] = l; + outbuffer[(i * 2) + 1] = r; + } ll = tl; lr = tr; @@ -1257,6 +1260,7 @@ void jar_mod_fillbuffer(jar_mod_context_t* modctx, gf_int16_t* outbuffer, unsign modctx->last_l_sample = ll; modctx->last_r_sample = lr; + skip:; modctx->samplenb = modctx->samplenb + nbsample; } else { for(i = 0; i < nbsample; i++) { @@ -1351,14 +1355,16 @@ mulong jar_mod_current_samples(jar_mod_context_t* modctx) { /* Works, however it is very slow, this data should be cached to ensure it is run only once per file */ mulong jar_mod_max_samples(jar_mod_context_t* ctx) { - mint buff[2]; mulong len; mulong lastcount = ctx->loopcount; - while(ctx->loopcount <= lastcount) jar_mod_fillbuffer(ctx, buff, 1, 0); + while(ctx->loopcount <= lastcount) jar_mod_fillbuffer(ctx, NULL, 1, 0); len = ctx->samplenb; jar_mod_seek_start(ctx); + ctx->samplenb = 0; + ctx->tablepos = 0; + ctx->patternpos = 0; return len; } diff --git a/engine/gf_audio.c b/engine/gf_audio.c index 1e4ba40..ced1c91 100644 --- a/engine/gf_audio.c +++ b/engine/gf_audio.c @@ -18,6 +18,8 @@ #include #include +const char* gf_audio_mod_sig[] = {"M!K!", "M.K.", "FLT4", "FLT8", "4CHN", "6CHN", "8CHN", "10CH", "12CH", "14CH", "16CH", "18CH", "20CH", "22CH", "24CH", "26CH", "28CH", "30CH", "32CH"}; + void gf_audio_callback(ma_device* dev, void* output, const void* input, ma_uint32 frame) { int i; gf_audio_t* audio = dev->pUserData; @@ -30,7 +32,7 @@ void gf_audio_callback(ma_device* dev, void* output, const void* input, ma_uint3 } for(i = 0; i < GF_AUDIO_MAX_DECODERS; i++) { - if(audio->decoder[i].used == 1) { + if(audio->decoder[i].used && audio->decoder[i].decoder != NULL) { ma_uint64 readframe; int j; ma_int16* r = malloc(sizeof(*r) * frame * 2); @@ -41,10 +43,37 @@ void gf_audio_callback(ma_device* dev, void* output, const void* input, ma_uint3 } free(r); if(frame > readframe) { - ma_decoder_uninit(audio->decoder[i].decoder); - free(audio->decoder[i].decoder); - audio->decoder[i].decoder = NULL; - audio->decoder[i].used = 0; + gf_audio_decoder_destroy(&audio->decoder[i]); + } + } else if(audio->decoder[i].used && audio->decoder[i].xm != NULL) { + int j; + int gotframe; + float* r = malloc(sizeof(*r) * frame * 2); + jar_xm_generate_samples(audio->decoder[i].xm, r, frame); + gotframe = audio->decoder[i].samples > frame ? frame : audio->decoder[i].samples; + for(j = 0; j < gotframe; j++) { + tmp[2 * j + 0] += (double)r[2 * j + 0]; + tmp[2 * j + 1] += (double)r[2 * j + 1]; + } + free(r); + audio->decoder[i].samples -= frame; + if(audio->decoder[i].samples <= 0) { + gf_audio_decoder_destroy(&audio->decoder[i]); + } + } else if(audio->decoder[i].used && audio->decoder[i].mod != NULL) { + int j; + int gotframe; + ma_int16* r = malloc(sizeof(*r) * frame * 2); + jar_mod_fillbuffer(audio->decoder[i].mod, r, frame, NULL); + gotframe = audio->decoder[i].samples > frame ? frame : audio->decoder[i].samples; + for(j = 0; j < gotframe; j++) { + tmp[2 * j + 0] += (double)r[2 * j + 0] / 32768.0; + tmp[2 * j + 1] += (double)r[2 * j + 1] / 32768.0; + } + free(r); + audio->decoder[i].samples -= frame; + if(audio->decoder[i].samples <= 0) { + gf_audio_decoder_destroy(&audio->decoder[i]); } } } @@ -60,6 +89,35 @@ int gf_audio_load(gf_audio_t* audio, const void* data, size_t size) { int i; for(i = 0; i < GF_AUDIO_MAX_DECODERS; i++) { if(audio->decoder[i].used == 0) { + int xm_cond = size > 37 && memcmp(data, "Extended Module: ", 17) == 0 && ((char*)data)[37] == 0x1a; + int mod_cond = size > 1080; + + if(mod_cond) { + int j; + for(j = 0; j < sizeof(gf_audio_mod_sig) / sizeof(gf_audio_mod_sig[0]); j++) { + mod_cond = mod_cond || (memcmp(data + 1080, gf_audio_mod_sig[j], 4) == 0); + } + } + + if(xm_cond) { + if(jar_xm_create_context_safe(&audio->decoder[i].xm, data, size, audio->device_config.sampleRate) == 0) { + audio->decoder[i].samples = jar_xm_get_remaining_samples(audio->decoder[i].xm); + audio->decoder[i].used = 1; + return 0; + } + audio->decoder[i].xm = NULL; + } else if(mod_cond) { + audio->decoder[i].mod = malloc(sizeof(*audio->decoder[i].mod)); + jar_mod_init(audio->decoder[i].mod); + jar_mod_setcfg(audio->decoder[i].mod, audio->device_config.sampleRate, 16, 1, 0, 0); + if(jar_mod_load(audio->decoder[i].mod, (void*)data, size)) { + audio->decoder[i].samples = jar_mod_max_samples(audio->decoder[i].mod); + audio->decoder[i].used = 1; + return 0; + } + free(audio->decoder[i].mod); + audio->decoder[i].mod = NULL; + } audio->decoder[i].decoder = malloc(sizeof(*audio->decoder[i].decoder)); if(ma_decoder_init_memory(data, size, &audio->decoder[i].decoder_config, audio->decoder[i].decoder) == MA_SUCCESS) { audio->decoder[i].used = 1; @@ -102,16 +160,20 @@ gf_audio_t* gf_audio_create(gf_engine_t* engine) { audio->device_config = ma_device_config_init(ma_device_type_playback); audio->device_config.playback.format = ma_format_s16; audio->device_config.playback.channels = 2; - audio->device_config.sampleRate = 44100; + audio->device_config.sampleRate = 48000; audio->device_config.dataCallback = gf_audio_callback; audio->device_config.pUserData = audio; for(i = 0; i < GF_AUDIO_MAX_DECODERS; i++) { audio->decoder[i].used = 0; audio->decoder[i].decoder = NULL; + audio->decoder[i].xm = NULL; + audio->decoder[i].mod = NULL; audio->decoder[i].decoder_config = ma_decoder_config_init(audio->device_config.playback.format, audio->device_config.playback.channels, audio->device_config.sampleRate); } + gf_audio_load_file(audio, "test.xm"); + audio->device = malloc(sizeof(*audio->device)); if(ma_device_init(NULL, &audio->device_config, audio->device) != MA_SUCCESS) { gf_log_function(engine, "Failed to open playback device", ""); @@ -132,6 +194,24 @@ gf_audio_t* gf_audio_create(gf_engine_t* engine) { return audio; } +void gf_audio_decoder_destroy(gf_audio_decoder_t* decoder) { + if(decoder->decoder != NULL) { + ma_decoder_uninit(decoder->decoder); + free(decoder->decoder); + decoder->decoder = NULL; + } + if(decoder->xm != NULL) { + jar_xm_free_context(decoder->xm); + decoder->xm = NULL; + } + if(decoder->mod != NULL) { + jar_mod_unload(decoder->mod); + free(decoder->mod); + decoder->mod = NULL; + } + decoder->used = 0; +} + void gf_audio_destroy(gf_audio_t* audio) { int i; if(audio->device != NULL) { @@ -139,10 +219,7 @@ void gf_audio_destroy(gf_audio_t* audio) { free(audio->device); } for(i = 0; i < GF_AUDIO_MAX_DECODERS; i++) { - if(audio->decoder[i].decoder != NULL) { - ma_decoder_uninit(audio->decoder[i].decoder); - free(audio->decoder[i].decoder); - } + gf_audio_decoder_destroy(&audio->decoder[i]); } gf_log_function(audio->engine, "Destroyed audio interface", ""); free(audio); diff --git a/engine/include/gf_audio.h b/engine/include/gf_audio.h index 2958e7e..05c76bf 100644 --- a/engine/include/gf_audio.h +++ b/engine/include/gf_audio.h @@ -34,6 +34,13 @@ GF_EXPORT gf_audio_t* gf_audio_create(gf_engine_t* engine); */ GF_EXPORT void gf_audio_destroy(gf_audio_t* audio); +/** + * @~english + * @brief Destroy audio decoder + * @param decoder Audio decoder + */ +GF_EXPORT void gf_audio_decoder_destroy(gf_audio_decoder_t* decoder); + /** * @~english * @brief Load and play file diff --git a/engine/include/gf_type/audio.h b/engine/include/gf_type/audio.h index 409b592..dfd3edf 100644 --- a/engine/include/gf_type/audio.h +++ b/engine/include/gf_type/audio.h @@ -16,6 +16,8 @@ typedef struct gf_audio_decoder_t gf_audio_decoder_t; /* External library */ #include +#include +#include /* Engine */ #include @@ -39,13 +41,25 @@ typedef struct gf_audio_decoder_t gf_audio_decoder_t; * @var gf_audio_decoder_t::decoder * @brief miniaudio decoder * + * @var gf_audio_decoder_t::xm + * @brief XM context + * + * @var gf_audio_decoder_t::mod + * @brief MOD context + * + * @var gf_audio_decoder_t::samples + * @brief Remaining samples + * * @var gf_audio_decoder_t::used * @brief `1` if used, otherwise `0` */ GF_DECLARE_TYPE(audio_decoder, { - ma_decoder_config decoder_config; - ma_decoder* decoder; - int used; + ma_decoder_config decoder_config; + ma_decoder* decoder; + jar_xm_context_t* xm; + jar_mod_context_t* mod; + int samples; + int used; }); /**