mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-27 16:10:48 +01:00
Fix some Ongakukan .adp + cleanup
This commit is contained in:
parent
b916d2685e
commit
33f01354e6
@ -1,221 +1,223 @@
|
||||
|
||||
/* Decodes Ongakukan ADPCM, found in their PS2 and PSP games.
|
||||
* Basically their take on ADPCM with some companding and quantization involved.
|
||||
*
|
||||
* Original decoder is a mix of COP0 and VU1 code, however PS2 floats aren't actually used (if at all)
|
||||
* when it comes to converting encoded sample data (consisting of a single byte with two 4-bit nibbles, respectively) to PCM16.
|
||||
*
|
||||
* The decoder you see here is a hand-crafted, faithful C adaptation of original MIPS R5900 (PS2) and R4000 (PSP) code, from various executables of their games.
|
||||
* As a consequence of all this, a new, entirely custom decoder had to be designed from the ground-up into vgmstream. No info surrounding this codec was available. */
|
||||
|
||||
/* Additional notes:
|
||||
* - This code does not support PCM16 sound data, in any way, shape, or form.
|
||||
* -- Ongakukan's internal sound engine from their PS2 and PSP games allow for only two codecs: signed PCM16, and their own take on ADPCM, respectively.
|
||||
* -- However, much of that support is reliant on a flag that's set to either one of the two codecs depending on the opened file extension.
|
||||
* Basically, how it works is: if sound data is "PCM16" (available to "wav" and "ads" files), set flag to 0.
|
||||
* If sound data is "ADPCM" (available to "adp" files), set it to 1.
|
||||
* Code handles this flag as a boolean var; 0 is "false" and 1 is "true".
|
||||
* -- As vgmstream has built-in support for the former codec (and the many metas that use it) however, despite being fairly easy to add here,
|
||||
* re-implementing one from scratch would be a wasted effort regardless; it is consequentially not included. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "../../util/reader_sf.h"
|
||||
#include "ongakukan_adp_lib.h"
|
||||
|
||||
/* the struct that oversees everything. */
|
||||
|
||||
struct ongakukan_adp_t
|
||||
{
|
||||
STREAMFILE* sf; /* streamfile var. */
|
||||
|
||||
long int data_offset; /* current offset of data that's being read. */
|
||||
long int start_offset; /* base offset of encoded sound data. */
|
||||
long int data_size; /* sound data size, basically ADP size if it didn't have 44 bytes more. */
|
||||
long int sample_work; /* total number of samples, calc'd using data_size as a base. */
|
||||
long int alt_sample_work1; /* represents current number of samples as they're decoded. */
|
||||
long int alt_sample_work2; /* represents the many samples left to go through. */
|
||||
long int samples_filled; /* how many samples were filled to vgmstream buffer. */
|
||||
long int samples_consumed; /* how many samples vgmstream buffer had to consume. */
|
||||
|
||||
bool sound_is_adpcm; /* false = no (see "additional notes" above) , true = yes */
|
||||
bool sample_startpoint_present; /* false = failed to make startpoint, true = startpoint present */
|
||||
char sample_mode; /* 0 = creates decoding setup, 1 = continue decoding data with setup in place */
|
||||
bool sample_pair_is_decoded; /* false = no, true = yes */
|
||||
|
||||
unsigned char base_pair; /* represents a read byte from ADPCM data, consisting of two 4-bit nibbles each.*/
|
||||
long int base_scale; /* how loud should this sample be. */
|
||||
short int sample_hist[2]; /* two pairs of signed 16-bit data, representing samples. yes, it is void. */
|
||||
};
|
||||
|
||||
/* filter table consisting of 16 numbers each. */
|
||||
|
||||
const short int ongakukan_adpcm_filter[16] = { 233, 549, 453, 375, 310, 233, 233, 233, 233, 233, 233, 233, 310, 375, 453, 549 };
|
||||
|
||||
/* streamfile read function declararion, more may be added in the future. */
|
||||
|
||||
static uint8_t read_u8_wrapper(ongakukan_adp_t* handle);
|
||||
|
||||
/* function declarations for the inner workings of codec data. */
|
||||
|
||||
static bool set_up_sample_startpoint(ongakukan_adp_t* handle);
|
||||
static void decode_ongakukan_adpcm_samples(ongakukan_adp_t* handle);
|
||||
|
||||
/* codec management functions, meant to oversee and supervise ADP data from the top-down.
|
||||
* in layman terms, they control how ADP data should be handled and when. */
|
||||
|
||||
ongakukan_adp_t* init_ongakukan_adpcm(STREAMFILE* sf, long int data_offset, long int data_size,
|
||||
bool sound_is_adpcm)
|
||||
{
|
||||
ongakukan_adp_t* handle = NULL;
|
||||
|
||||
/* allocate handle using malloc. */
|
||||
handle = malloc(sizeof(ongakukan_adp_t));
|
||||
if (!handle) goto fail;
|
||||
|
||||
/* now, to set up the rest of the handle with the data we have... */
|
||||
handle->sf = sf;
|
||||
handle->data_offset = data_offset;
|
||||
handle->start_offset = data_offset;
|
||||
handle->data_size = data_size;
|
||||
handle->sample_mode = 0;
|
||||
handle->sound_is_adpcm = sound_is_adpcm;
|
||||
handle->sample_startpoint_present = set_up_sample_startpoint(handle);
|
||||
/* if we failed in planting up the seeds for an ADPCM decoder, we simply throw in the towel and take a walk in the park. */
|
||||
if (handle->sample_startpoint_present == false) { goto fail; }
|
||||
|
||||
return handle;
|
||||
fail:
|
||||
ongakukan_adpcm_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ongakukan_adpcm_free(ongakukan_adp_t* handle)
|
||||
{
|
||||
if (!handle) return;
|
||||
free(handle);
|
||||
}
|
||||
|
||||
void ongakukan_adpcm_reset(ongakukan_adp_t* handle)
|
||||
{
|
||||
if (!handle) return;
|
||||
|
||||
/* wipe out certain values from handle so we can start over. */
|
||||
handle->data_offset = handle->start_offset;
|
||||
handle->sample_pair_is_decoded = false;
|
||||
handle->sample_mode = 0;
|
||||
handle->alt_sample_work1 = 0;
|
||||
handle->alt_sample_work2 = handle->sample_work;
|
||||
}
|
||||
|
||||
void ongakukan_adpcm_seek(ongakukan_adp_t* handle, long int target_sample)
|
||||
{
|
||||
if (!handle) return;
|
||||
|
||||
char ts_modulus = 0; /* ts_modulus is here to ensure target_sample gets rounded to a multiple of 2. */
|
||||
long int ts_data_offset = 0; /* ts_data_offset is basically data_offset but with (left(if PCM)/right(if ADPCM))-shifted target_sample calc by 1. */
|
||||
ts_data_offset = target_sample >> 1;
|
||||
ts_modulus = target_sample % 2;
|
||||
target_sample = target_sample - ts_modulus;
|
||||
/* if ADPCM, right-shift the former first then have ts_modulus calc remainder of target_sample by 2 so we can subtract it with ts_modulus.
|
||||
* this is needed for the two counters that the decoder has that can both add and subtract with 2, respectively
|
||||
* (and in order, too; meaning one counter does "plus 2" while the other does "minus 2",
|
||||
* and though they're fairly useless ATM, you pretty much want to leave them alone). */
|
||||
|
||||
/* anyway, we'll have to tell decoder that target_sample is calling and wants to go somewhere right now,
|
||||
* so we'll have data_offset reposition itself to where sound data for that sample ought to be
|
||||
* and (as of now) reset basically all decode state up to this point so we can continue to decode all sample pairs without issue. */
|
||||
handle->data_offset = handle->start_offset + ts_data_offset;
|
||||
handle->sample_pair_is_decoded = false;
|
||||
handle->sample_mode = 0;
|
||||
handle->alt_sample_work1 = target_sample;
|
||||
handle->alt_sample_work2 = handle->sample_work - target_sample;
|
||||
|
||||
/* for now, just do what reset_all_ongakukan_adpcm does but for the current sample instead of literally everything.
|
||||
* seek_ongakukan_adpcm_pos in its current state is a bit more involved than the above, but works. */
|
||||
}
|
||||
|
||||
long int get_num_samples_from_ongakukan_adpcm(ongakukan_adp_t* handle)
|
||||
{
|
||||
if (!handle) return 0;
|
||||
return handle->sample_work;
|
||||
}
|
||||
|
||||
void* get_sample_hist_from_ongakukan_adpcm(ongakukan_adp_t* handle)
|
||||
{
|
||||
if (!handle) return 0;
|
||||
return &handle->sample_hist;
|
||||
}
|
||||
|
||||
/* function definitions for the inner workings of codec data. */
|
||||
|
||||
static bool set_up_sample_startpoint(ongakukan_adp_t* handle)
|
||||
{
|
||||
/* make decoder fail hard if streamfile object isn't opened or downright useless. */
|
||||
if (!handle->sf) return false;
|
||||
|
||||
if (handle->sound_is_adpcm == 0) { return false; }
|
||||
else { /* num_samples but for Ongakukan ADPCM sound data. */ handle->sample_work = handle->data_size << 1; }
|
||||
/* set "beginning" and "end" sample vars and send a "message" that we went through no sample yet.*/
|
||||
handle->alt_sample_work1 = 0;
|
||||
handle->alt_sample_work2 = handle->sample_work;
|
||||
handle->sample_pair_is_decoded = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void decode_ongakukan_adpcm_data(ongakukan_adp_t* handle)
|
||||
{
|
||||
/* set samples_filled to 0 and have our decoder go through every sample that exists in the sound data.*/
|
||||
decode_ongakukan_adpcm_samples(handle);
|
||||
/* if setup is established for further decoding, switch gears and have the decoder use that setup for as long as possible. */
|
||||
/* if sample pair is decoded, advance to next byte, tell our handle that we went through 2 samples and make decoder go through next available data again. */
|
||||
if (handle->sample_pair_is_decoded == true)
|
||||
{
|
||||
handle->data_offset++;
|
||||
handle->alt_sample_work1 += 2;
|
||||
handle->alt_sample_work2 -= 2;
|
||||
handle->sample_pair_is_decoded = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_ongakukan_adpcm_samples(ongakukan_adp_t* handle)
|
||||
{
|
||||
unsigned char nibble1 = 0, nibble2 = 0; /* two chars representing a 4-bit nibble. */
|
||||
long int nibble1_1 = 0, nibble2_1 = 0; /* two long ints representing pure sample data. */
|
||||
|
||||
if (handle->sample_pair_is_decoded == false)
|
||||
{
|
||||
/* sample_mode being 0 means we can just do a setup for future sample decoding so we have nothing to worry about in the future. */
|
||||
if (handle->sample_mode == 0)
|
||||
{
|
||||
/* set "base scale", two "sample hist"s, and "base pair", respectively. */
|
||||
handle->base_scale = 0x10;
|
||||
handle->sample_hist[0] = 0;
|
||||
handle->sample_hist[1] = 0;
|
||||
handle->base_pair = 0;
|
||||
handle->sample_mode = 1; /* indicates we have the setup we need to decode samples. */
|
||||
}
|
||||
handle->base_pair = (uint8_t)read_u8_wrapper(handle);
|
||||
|
||||
nibble1 = handle->base_pair & 0xf;
|
||||
nibble1_1 = nibble1 + -8;
|
||||
nibble2 = (handle->base_pair >> 4) & 0xf;
|
||||
nibble2_1 = nibble2 + -8;
|
||||
nibble2_1 = nibble2_1 * handle->base_scale;
|
||||
handle->sample_hist[0] = handle->sample_hist[1] + nibble2_1;
|
||||
handle->base_scale = (handle->base_scale * (ongakukan_adpcm_filter[nibble2])) >> 8;
|
||||
nibble1_1 = nibble1_1 * handle->base_scale;
|
||||
handle->sample_hist[1] = handle->sample_hist[0] + nibble1_1;
|
||||
handle->base_scale = (handle->base_scale * (ongakukan_adpcm_filter[nibble1])) >> 8;
|
||||
handle->sample_pair_is_decoded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* streamfile read function definitions at the very bottom. */
|
||||
|
||||
static uint8_t read_u8_wrapper(ongakukan_adp_t* handle)
|
||||
{
|
||||
if ((handle->data_offset - handle->start_offset) > handle->data_size) return 0;
|
||||
if ((handle->data_offset - handle->start_offset) < 0) return 0;
|
||||
return read_u8((off_t)(handle->data_offset), handle->sf);
|
||||
}
|
||||
|
||||
/* Decodes Ongakukan ADPCM, found in their PS2 and PSP games.
|
||||
* Basically their take on ADPCM with some companding and quantization involved.
|
||||
*
|
||||
* Original decoder is a mix of COP0 and VU1 code, however PS2 floats aren't actually used (if at all)
|
||||
* when it comes to converting encoded sample data (consisting of a single byte with two 4-bit nibbles, respectively) to PCM16.
|
||||
*
|
||||
* The decoder you see here is a hand-crafted, faithful C adaptation of original MIPS R5900 (PS2) and R4000 (PSP) code, from various executables of their games.
|
||||
* As a consequence of all this, a new, entirely custom decoder had to be designed from the ground-up into vgmstream. No info surrounding this codec was available. */
|
||||
|
||||
/* Additional notes:
|
||||
* - This code does not support PCM16 sound data, in any way, shape, or form.
|
||||
* -- Ongakukan's internal sound engine from their PS2 and PSP games allow for only two codecs: signed PCM16, and their own take on ADPCM, respectively.
|
||||
* -- However, much of that support is reliant on a flag that's set to either one of the two codecs depending on the opened file extension.
|
||||
* Basically, how it works is: if sound data is "PCM16" (available to "wav" and "ads" files), set flag to 0.
|
||||
* If sound data is "ADPCM" (available to "adp" files), set it to 1.
|
||||
* Code handles this flag as a boolean var; 0 is "false" and 1 is "true".
|
||||
* -- As vgmstream has built-in support for the former codec (and the many metas that use it) however, despite being fairly easy to add here,
|
||||
* re-implementing one from scratch would be a wasted effort regardless; it is consequentially not included. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "../../util/reader_sf.h"
|
||||
#include "ongakukan_adp_lib.h"
|
||||
|
||||
/* the struct that oversees everything. */
|
||||
|
||||
struct ongakukan_adp_t
|
||||
{
|
||||
STREAMFILE* sf; /* streamfile var. */
|
||||
|
||||
long int data_offset; /* current offset of data that's being read. */
|
||||
long int start_offset; /* base offset of encoded sound data. */
|
||||
long int data_size; /* sound data size, basically ADP size if it didn't have 44 bytes more. */
|
||||
long int sample_work; /* total number of samples, calc'd using data_size as a base. */
|
||||
long int alt_sample_work1; /* represents current number of samples as they're decoded. */
|
||||
long int alt_sample_work2; /* represents the many samples left to go through. */
|
||||
long int samples_filled; /* how many samples were filled to vgmstream buffer. */
|
||||
long int samples_consumed; /* how many samples vgmstream buffer had to consume. */
|
||||
|
||||
bool sound_is_adpcm; /* false = no (see "additional notes" above) , true = yes */
|
||||
bool sample_startpoint_present; /* false = failed to make startpoint, true = startpoint present */
|
||||
char sample_mode; /* 0 = creates decoding setup, 1 = continue decoding data with setup in place */
|
||||
bool sample_pair_is_decoded; /* false = no, true = yes */
|
||||
|
||||
unsigned char base_pair; /* represents a read byte from ADPCM data, consisting of two 4-bit nibbles each.*/
|
||||
long int base_scale; /* how loud should this sample be. */
|
||||
short int sample_hist[2]; /* two pairs of signed 16-bit data, representing samples. yes, it is void. */
|
||||
};
|
||||
|
||||
/* filter table consisting of 16 numbers each. */
|
||||
|
||||
static const short int ongakukan_adpcm_filter[16] = { 233, 549, 453, 375, 310, 233, 233, 233, 233, 233, 233, 233, 310, 375, 453, 549 };
|
||||
|
||||
/* streamfile read function declararion, more may be added in the future. */
|
||||
|
||||
static uint8_t read_u8_wrapper(ongakukan_adp_t* handle);
|
||||
|
||||
/* function declarations for the inner workings of codec data. */
|
||||
|
||||
static bool set_up_sample_startpoint(ongakukan_adp_t* handle);
|
||||
static void decode_ongakukan_adpcm_samples(ongakukan_adp_t* handle);
|
||||
|
||||
/* codec management functions, meant to oversee and supervise ADP data from the top-down.
|
||||
* in layman terms, they control how ADP data should be handled and when. */
|
||||
|
||||
ongakukan_adp_t* ongakukan_adpcm_init(STREAMFILE* sf, long int data_offset, long int data_size, bool sound_is_adpcm)
|
||||
{
|
||||
ongakukan_adp_t* handle = NULL;
|
||||
|
||||
if (!sound_is_adpcm)
|
||||
return NULL;
|
||||
|
||||
/* allocate handle. */
|
||||
handle = calloc(1, sizeof(ongakukan_adp_t));
|
||||
if (!handle) goto fail;
|
||||
|
||||
/* now, to set up the rest of the handle with the data we have... */
|
||||
handle->sf = sf;
|
||||
handle->data_offset = data_offset;
|
||||
handle->start_offset = data_offset;
|
||||
handle->data_size = data_size;
|
||||
handle->sample_mode = 0;
|
||||
handle->sound_is_adpcm = sound_is_adpcm;
|
||||
handle->sample_startpoint_present = set_up_sample_startpoint(handle);
|
||||
/* if we failed in planting up the seeds for an ADPCM decoder, we simply throw in the towel and take a walk in the park. */
|
||||
if (handle->sample_startpoint_present == false) { goto fail; }
|
||||
|
||||
return handle;
|
||||
fail:
|
||||
ongakukan_adpcm_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ongakukan_adpcm_free(ongakukan_adp_t* handle)
|
||||
{
|
||||
if (!handle) return;
|
||||
free(handle);
|
||||
}
|
||||
|
||||
void ongakukan_adpcm_reset(ongakukan_adp_t* handle)
|
||||
{
|
||||
if (!handle) return;
|
||||
|
||||
/* wipe out certain values from handle so we can start over. */
|
||||
handle->data_offset = handle->start_offset;
|
||||
handle->sample_pair_is_decoded = false;
|
||||
handle->sample_mode = 0;
|
||||
handle->alt_sample_work1 = 0;
|
||||
handle->alt_sample_work2 = handle->sample_work;
|
||||
}
|
||||
|
||||
void ongakukan_adpcm_seek(ongakukan_adp_t* handle, long int target_sample)
|
||||
{
|
||||
if (!handle) return;
|
||||
|
||||
char ts_modulus = 0; /* ts_modulus is here to ensure target_sample gets rounded to a multiple of 2. */
|
||||
long int ts_data_offset = 0; /* ts_data_offset is basically data_offset but with (left(if PCM)/right(if ADPCM))-shifted target_sample calc by 1. */
|
||||
ts_data_offset = target_sample >> 1;
|
||||
ts_modulus = target_sample % 2;
|
||||
target_sample = target_sample - ts_modulus;
|
||||
/* if ADPCM, right-shift the former first then have ts_modulus calc remainder of target_sample by 2 so we can subtract it with ts_modulus.
|
||||
* this is needed for the two counters that the decoder has that can both add and subtract with 2, respectively
|
||||
* (and in order, too; meaning one counter does "plus 2" while the other does "minus 2",
|
||||
* and though they're fairly useless ATM, you pretty much want to leave them alone). */
|
||||
|
||||
/* anyway, we'll have to tell decoder that target_sample is calling and wants to go somewhere right now,
|
||||
* so we'll have data_offset reposition itself to where sound data for that sample ought to be
|
||||
* and (as of now) reset basically all decode state up to this point so we can continue to decode all sample pairs without issue. */
|
||||
handle->data_offset = handle->start_offset + ts_data_offset;
|
||||
handle->sample_pair_is_decoded = false;
|
||||
handle->sample_mode = 0;
|
||||
handle->alt_sample_work1 = target_sample;
|
||||
handle->alt_sample_work2 = handle->sample_work - target_sample;
|
||||
|
||||
/* for now, just do what reset_all_ongakukan_adpcm does but for the current sample instead of literally everything.
|
||||
* seek_ongakukan_adpcm_pos in its current state is a bit more involved than the above, but works. */
|
||||
}
|
||||
|
||||
long int ongakukan_adpcm_get_num_samples(ongakukan_adp_t* handle)
|
||||
{
|
||||
if (!handle) return 0;
|
||||
return handle->sample_work;
|
||||
}
|
||||
|
||||
short* ongakukan_adpcm_get_sample_hist(ongakukan_adp_t* handle)
|
||||
{
|
||||
if (!handle) return 0;
|
||||
return handle->sample_hist;
|
||||
}
|
||||
|
||||
/* function definitions for the inner workings of codec data. */
|
||||
|
||||
static bool set_up_sample_startpoint(ongakukan_adp_t* handle)
|
||||
{
|
||||
/* make decoder fail hard if streamfile object isn't opened or downright useless. */
|
||||
if (!handle->sf) return false;
|
||||
|
||||
if (handle->sound_is_adpcm == 0) { return false; }
|
||||
else { /* num_samples but for Ongakukan ADPCM sound data. */ handle->sample_work = handle->data_size << 1; }
|
||||
/* set "beginning" and "end" sample vars and send a "message" that we went through no sample yet.*/
|
||||
handle->alt_sample_work1 = 0;
|
||||
handle->alt_sample_work2 = handle->sample_work;
|
||||
handle->sample_pair_is_decoded = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ongakukan_adpcm_decode_data(ongakukan_adp_t* handle)
|
||||
{
|
||||
/* set samples_filled to 0 and have our decoder go through every sample that exists in the sound data.*/
|
||||
decode_ongakukan_adpcm_samples(handle);
|
||||
/* if setup is established for further decoding, switch gears and have the decoder use that setup for as long as possible. */
|
||||
/* if sample pair is decoded, advance to next byte, tell our handle that we went through 2 samples and make decoder go through next available data again. */
|
||||
if (handle->sample_pair_is_decoded == true)
|
||||
{
|
||||
handle->data_offset++;
|
||||
handle->alt_sample_work1 += 2;
|
||||
handle->alt_sample_work2 -= 2;
|
||||
handle->sample_pair_is_decoded = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_ongakukan_adpcm_samples(ongakukan_adp_t* handle)
|
||||
{
|
||||
unsigned char nibble1 = 0, nibble2 = 0; /* two chars representing a 4-bit nibble. */
|
||||
long int nibble1_1 = 0, nibble2_1 = 0; /* two long ints representing pure sample data. */
|
||||
|
||||
if (handle->sample_pair_is_decoded == false)
|
||||
{
|
||||
/* sample_mode being 0 means we can just do a setup for future sample decoding so we have nothing to worry about in the future. */
|
||||
if (handle->sample_mode == 0)
|
||||
{
|
||||
/* set "base scale", two "sample hist"s, and "base pair", respectively. */
|
||||
handle->base_scale = 0x10;
|
||||
handle->sample_hist[0] = 0;
|
||||
handle->sample_hist[1] = 0;
|
||||
handle->base_pair = 0;
|
||||
handle->sample_mode = 1; /* indicates we have the setup we need to decode samples. */
|
||||
}
|
||||
handle->base_pair = (uint8_t)read_u8_wrapper(handle);
|
||||
|
||||
nibble1 = handle->base_pair & 0xf;
|
||||
nibble1_1 = nibble1 + -8;
|
||||
nibble2 = (handle->base_pair >> 4) & 0xf;
|
||||
nibble2_1 = nibble2 + -8;
|
||||
nibble2_1 = nibble2_1 * handle->base_scale;
|
||||
handle->sample_hist[0] = handle->sample_hist[1] + nibble2_1;
|
||||
handle->base_scale = (handle->base_scale * (ongakukan_adpcm_filter[nibble2])) >> 8;
|
||||
nibble1_1 = nibble1_1 * handle->base_scale;
|
||||
handle->sample_hist[1] = handle->sample_hist[0] + nibble1_1;
|
||||
handle->base_scale = (handle->base_scale * (ongakukan_adpcm_filter[nibble1])) >> 8;
|
||||
handle->sample_pair_is_decoded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* streamfile read function definitions at the very bottom. */
|
||||
|
||||
static uint8_t read_u8_wrapper(ongakukan_adp_t* handle)
|
||||
{
|
||||
if ((handle->data_offset - handle->start_offset) > handle->data_size) return 0;
|
||||
if ((handle->data_offset - handle->start_offset) < 0) return 0;
|
||||
return read_u8((off_t)(handle->data_offset), handle->sf);
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
#ifndef _ONGAKUKAN_ADP_LIB_H_
|
||||
#define _ONGAKUKAN_ADP_LIB_H_
|
||||
|
||||
/* Ongakukan ADPCM codec, found in PS2 and PSP games. */
|
||||
|
||||
#include "../../util/reader_sf.h"
|
||||
|
||||
/* typedef struct */
|
||||
typedef struct ongakukan_adp_t ongakukan_adp_t;
|
||||
|
||||
/* function declaration for we need to set up the codec data. */
|
||||
ongakukan_adp_t* init_ongakukan_adpcm(STREAMFILE* sf, long int data_offset, long int data_size,
|
||||
bool sound_is_adpcm);
|
||||
|
||||
/* function declaration for freeing all memory related to ongakukan_adp_t struct var. */
|
||||
void ongakukan_adpcm_free(ongakukan_adp_t* handle);
|
||||
void ongakukan_adpcm_reset(ongakukan_adp_t* handle);
|
||||
void ongakukan_adpcm_seek(ongakukan_adp_t* handle, long int target_sample);
|
||||
|
||||
/* function declaration for when we need to get (and send) certain values from ongakukan_adp_t handle */
|
||||
long int get_num_samples_from_ongakukan_adpcm(ongakukan_adp_t* handle);
|
||||
void* get_sample_hist_from_ongakukan_adpcm(ongakukan_adp_t* handle);
|
||||
|
||||
/* function declaration for actually decoding samples, can't be that hard, right? */
|
||||
void decode_ongakukan_adpcm_data(ongakukan_adp_t* handle);
|
||||
|
||||
#endif // _ONGAKUKAN_ADP_LIB_H_
|
||||
#define _ONGAKUKAN_ADP_LIB_H_
|
||||
|
||||
/* Ongakukan ADPCM codec, found in PS2 and PSP games. */
|
||||
|
||||
#include "../../util/reader_sf.h"
|
||||
|
||||
/* typedef struct */
|
||||
typedef struct ongakukan_adp_t ongakukan_adp_t;
|
||||
|
||||
/* function declaration for we need to set up the codec data. */
|
||||
ongakukan_adp_t* ongakukan_adpcm_init(STREAMFILE* sf, long int data_offset, long int data_size,
|
||||
bool sound_is_adpcm);
|
||||
|
||||
/* function declaration for freeing all memory related to ongakukan_adp_t struct var. */
|
||||
void ongakukan_adpcm_free(ongakukan_adp_t* handle);
|
||||
void ongakukan_adpcm_reset(ongakukan_adp_t* handle);
|
||||
void ongakukan_adpcm_seek(ongakukan_adp_t* handle, long int target_sample);
|
||||
|
||||
/* function declaration for when we need to get (and send) certain values from ongakukan_adp_t handle */
|
||||
long int ongakukan_adpcm_get_num_samples(ongakukan_adp_t* handle);
|
||||
short* ongakukan_adpcm_get_sample_hist(ongakukan_adp_t* handle);
|
||||
|
||||
/* function declaration for actually decoding samples, can't be that hard, right? */
|
||||
void ongakukan_adpcm_decode_data(ongakukan_adp_t* handle);
|
||||
|
||||
#endif
|
||||
|
@ -1,89 +1,76 @@
|
||||
#include <stdlib.h>
|
||||
#include "coding.h"
|
||||
#include "libs/ongakukan_adp_lib.h"
|
||||
|
||||
struct ongakukan_adp_data
|
||||
{
|
||||
void* handle;
|
||||
int16_t* samples;
|
||||
int32_t samples_done;
|
||||
bool samples_filled; /* false - no, true - yes */
|
||||
int32_t getting_samples; /* initialized to 2 on decode_ongakukan_adp. i mean, we literally get two decoded samples here. */
|
||||
STREAMFILE* sf;
|
||||
};
|
||||
|
||||
ongakukan_adp_data* init_ongakukan_adp(STREAMFILE* sf, int32_t data_offset, int32_t data_size,
|
||||
bool sound_is_adpcm)
|
||||
{
|
||||
ongakukan_adp_data* data = NULL;
|
||||
|
||||
data = calloc(1, sizeof(ongakukan_adp_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
/* reopen STREAMFILE from here, then pass it as an argument for our init function. */
|
||||
data->sf = reopen_streamfile(sf, 0);
|
||||
if (!data->sf) goto fail;
|
||||
data->handle = init_ongakukan_adpcm(data->sf, (long int)(data_offset), (long int)(data_size),
|
||||
sound_is_adpcm);
|
||||
if (!data->handle) goto fail;
|
||||
|
||||
return data;
|
||||
fail:
|
||||
free_ongakukan_adp(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void decode_ongakukan_adp(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do)
|
||||
{
|
||||
ongakukan_adp_data* data = vgmstream->codec_data;
|
||||
|
||||
data->getting_samples = 2;
|
||||
data->samples_done = 0;
|
||||
data->samples_filled = false;
|
||||
/* ^ samples_filled is boolean here because we need to simplify how decoding will work here.
|
||||
* so, rather than making samples_filled into a long int counter,
|
||||
* we make it into a boolean flag instead so as to let data->samples_done shine as a counter
|
||||
* and the decoder to do its job without worry. */
|
||||
|
||||
while (data->samples_done < samples_to_do)
|
||||
{
|
||||
if (data->samples_filled)
|
||||
{
|
||||
memcpy(outbuf + data->samples_done,
|
||||
data->samples,
|
||||
data->getting_samples * sizeof(int16_t));
|
||||
data->samples_done += data->getting_samples;
|
||||
|
||||
data->samples_filled = false;
|
||||
}
|
||||
else { decode_ongakukan_adpcm_data(data->handle);
|
||||
data->samples_filled = true;
|
||||
data->samples = (int16_t*)get_sample_hist_from_ongakukan_adpcm(data->handle); }
|
||||
}
|
||||
}
|
||||
|
||||
void reset_ongakukan_adp(ongakukan_adp_data* data)
|
||||
{
|
||||
if (!data) return;
|
||||
ongakukan_adpcm_reset(data->handle);
|
||||
}
|
||||
|
||||
void seek_ongakukan_adp(ongakukan_adp_data* data, int32_t current_sample)
|
||||
{
|
||||
if (!data) return;
|
||||
ongakukan_adpcm_seek(data->handle, current_sample);
|
||||
}
|
||||
|
||||
void free_ongakukan_adp(ongakukan_adp_data* data)
|
||||
{
|
||||
if (!data) return;
|
||||
close_streamfile(data->sf);
|
||||
ongakukan_adpcm_free(data->handle);
|
||||
free(data);
|
||||
}
|
||||
|
||||
int32_t ongakukan_adp_get_samples(ongakukan_adp_data* data)
|
||||
{
|
||||
if (!data) return 0;
|
||||
return (int32_t)get_num_samples_from_ongakukan_adpcm(data->handle);
|
||||
}
|
||||
#include <stdlib.h>
|
||||
#include "coding.h"
|
||||
#include "libs/ongakukan_adp_lib.h"
|
||||
|
||||
struct ongakukan_adp_data {
|
||||
void* handle;
|
||||
int16_t* samples;
|
||||
int32_t samples_done;
|
||||
bool samples_filled; /* false - no, true - yes */
|
||||
int32_t getting_samples; /* initialized to 2 on decode_ongakukan_adp. i mean, we literally get two decoded samples here. */
|
||||
STREAMFILE* sf;
|
||||
};
|
||||
|
||||
ongakukan_adp_data* init_ongakukan_adp(STREAMFILE* sf, int32_t data_offset, int32_t data_size, bool sound_is_adpcm) {
|
||||
ongakukan_adp_data* data = NULL;
|
||||
|
||||
data = calloc(1, sizeof(ongakukan_adp_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
/* reopen STREAMFILE from here, then pass it as an argument for our init function. */
|
||||
data->sf = reopen_streamfile(sf, 0);
|
||||
if (!data->sf) goto fail;
|
||||
|
||||
data->handle = ongakukan_adpcm_init(data->sf, (long int)(data_offset), (long int)(data_size), sound_is_adpcm);
|
||||
if (!data->handle) goto fail;
|
||||
|
||||
return data;
|
||||
fail:
|
||||
free_ongakukan_adp(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void decode_ongakukan_adp(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do) {
|
||||
ongakukan_adp_data* data = vgmstream->codec_data;
|
||||
|
||||
data->getting_samples = 2;
|
||||
data->samples_done = 0;
|
||||
data->samples_filled = false;
|
||||
|
||||
while (data->samples_done < samples_to_do) {
|
||||
if (data->samples_filled) {
|
||||
memcpy(outbuf + data->samples_done, data->samples, data->getting_samples * sizeof(int16_t));
|
||||
data->samples_done += data->getting_samples;
|
||||
|
||||
data->samples_filled = false;
|
||||
}
|
||||
else {
|
||||
ongakukan_adpcm_decode_data(data->handle);
|
||||
|
||||
data->samples_filled = true;
|
||||
data->samples = ongakukan_adpcm_get_sample_hist(data->handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset_ongakukan_adp(ongakukan_adp_data* data) {
|
||||
if (!data) return;
|
||||
ongakukan_adpcm_reset(data->handle);
|
||||
}
|
||||
|
||||
void seek_ongakukan_adp(ongakukan_adp_data* data, int32_t current_sample) {
|
||||
if (!data) return;
|
||||
ongakukan_adpcm_seek(data->handle, current_sample);
|
||||
}
|
||||
|
||||
void free_ongakukan_adp(ongakukan_adp_data* data) {
|
||||
if (!data) return;
|
||||
close_streamfile(data->sf);
|
||||
ongakukan_adpcm_free(data->handle);
|
||||
free(data);
|
||||
}
|
||||
|
||||
int32_t ongakukan_adp_get_samples(ongakukan_adp_data* data) {
|
||||
if (!data) return 0;
|
||||
return (int32_t)ongakukan_adpcm_get_num_samples(data->handle);
|
||||
}
|
||||
|
@ -1,103 +1,84 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* Ongakukan RIFF with "ADP" extension [Train Simulator - Midousuji-sen (PS2)] */
|
||||
VGMSTREAM* init_vgmstream_ongakukan_adp(STREAMFILE* sf)
|
||||
{
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
size_t file_size;
|
||||
bool has_data_chunk = false, has_fact_chunk = false;
|
||||
int loop_flag = 0;
|
||||
int riff_wave_header_size = 0x2c;
|
||||
/* ^ where sound data begins, as a consequence their tools couldn't even write full RIFF WAVE header to file beyond that point.. */
|
||||
bool sound_is_adpcm = false;
|
||||
int32_t supposed_size, fmt_size, fmt_offset, offset_of_supposed_last_chunk;
|
||||
int32_t sample_rate, data_size;
|
||||
int16_t num_channels, block_size;
|
||||
|
||||
/* RIFF+WAVE checks */
|
||||
if (!is_id32be(0x00, sf, "RIFF")) goto fail;
|
||||
if (!is_id32be(0x08, sf, "WAVE")) goto fail;
|
||||
/* WAVE "fmt " check */
|
||||
if (!is_id32be(0x0c, sf, "fmt ")) goto fail;
|
||||
/* "adp" extension check (literally only one) */
|
||||
if (!check_extensions(sf, "adp")) goto fail;
|
||||
|
||||
/* catch adp file size from here and use it whenever needed. */
|
||||
file_size = get_streamfile_size(sf);
|
||||
|
||||
/* RIFF size from adp file can go beyond actual size (e.g: reported 10MB vs 2MB). do quick calcs around this. */
|
||||
supposed_size = ((read_s32le(0x04, sf) - 0x24) >> 2) + 0x2c;
|
||||
if (file_size != supposed_size) goto fail;
|
||||
|
||||
/* read entire WAVE "fmt " chunk. we start by reading fmt_size from yours truly and setting fmt_offset. */
|
||||
fmt_size = read_s32le(0x10, sf);
|
||||
fmt_offset = 0x14;
|
||||
if ((fmt_size >= 0x10) && (fmt_size <= 0x12)) /* depending on the adp, fmt_size alternates between 0x10 and 0x12 */
|
||||
{
|
||||
if (read_s16le(fmt_offset + 0, sf) != 1) goto fail; /* chunk reports codec number as signed little-endian PCM, couldn't be more wrong. */
|
||||
num_channels = read_s16le(fmt_offset + 2, sf);
|
||||
sample_rate = read_s32le(fmt_offset + 4, sf);
|
||||
if (read_s16le(fmt_offset + 14, sf) != 0x10) goto fail; /* bit depth as chunk reports it. */
|
||||
/* rest of fmt header is the usual header for 16-bit PCM wav files: bitrate, block size, and the like (see riff.c) */
|
||||
/* if fmt_size == 0x12 there is an additional s16 field that's always zero. */
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* now calc the var so we can read either "data" or "fact" chunk; */
|
||||
offset_of_supposed_last_chunk = fmt_offset + fmt_size;
|
||||
|
||||
/* we need to get to the last WAVE chunk manually, and that means the calc below. */
|
||||
offset_of_supposed_last_chunk = fmt_offset + fmt_size;
|
||||
if (is_id32be(offset_of_supposed_last_chunk + 0, sf, "data")) has_data_chunk = true;
|
||||
if (is_id32be(offset_of_supposed_last_chunk + 0, sf, "fact")) has_fact_chunk = true;
|
||||
|
||||
/* and because sound data *must* start at 0x2c, they have to bork both chunks too, so they're now essentially useless.
|
||||
* they're basically leftovers from original (lossless) WAV files at this point. */
|
||||
if (has_data_chunk)
|
||||
{
|
||||
/* RIFF adp files have leftover "data" chunk size... that does NOT match the ADP file size at hand. */
|
||||
supposed_size = (read_s32le(offset_of_supposed_last_chunk + 4, sf) >> 2) + 0x2c;
|
||||
if (file_size != supposed_size) goto fail;
|
||||
}
|
||||
|
||||
if (has_fact_chunk)
|
||||
{
|
||||
/* RIFF adp files have also cut off "fact" chunk so we're just left with a useless number now. */
|
||||
if (read_s16le(offset_of_supposed_last_chunk + 4, sf) != 4) goto fail;
|
||||
}
|
||||
|
||||
/* set start_offset value to riff_wave_header_size and calculate data_size by ourselves, basically how Ongakukan does it also. */
|
||||
start_offset = riff_wave_header_size;
|
||||
data_size = (int32_t)(file_size) - riff_wave_header_size;
|
||||
|
||||
/* Ongagukan games using this format just read it by checking "ADP" extension in an provided file name of a programmer's own choosing,
|
||||
* and if extension is there they just read the reported "number of samples" and "sample_rate" vars
|
||||
* from RIFF WAVE "fmt " chunk based on an already-opened file with that same name.
|
||||
* and they don't even read RIFF chunks, they just pick these two vars and that's basically it. */
|
||||
|
||||
/* our custom decoder needs at least one flag set. */
|
||||
sound_is_adpcm = true;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(num_channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_ONGAKUKAN_RIFF_ADP;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->codec_data = init_ongakukan_adp(sf, start_offset, data_size, sound_is_adpcm);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ONGAKUKAN_ADPCM;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_samples = ongakukan_adp_get_samples(vgmstream->codec_data);
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* Ongakukan RIFF with "ADP" extension [Train Simulator: Midousuji-sen (PS2), Mobile Train Simulator (PSP)] */
|
||||
VGMSTREAM* init_vgmstream_ongakukan_adp(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0;
|
||||
bool sound_is_adpcm = false;
|
||||
int32_t fmt_size, fmt_offset;
|
||||
int32_t sample_rate, data_size;
|
||||
int16_t channels;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "RIFF"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "adp"))
|
||||
return NULL;
|
||||
|
||||
/* Format starts like RIFF but doesn't have valid chunks beyond fmt (ADPCM data overwrites anything after 0x2c). */
|
||||
|
||||
start_offset = 0x2c; /* fixed values, basically how Ongakukan does it */
|
||||
data_size = get_streamfile_size(sf) - start_offset;
|
||||
|
||||
/* RIFF size seem to match original PCM .wav, while encoded .adp data equals or is slightly smaller that that */
|
||||
uint32_t expected_size = (read_u32le(0x04, sf) - 0x24);
|
||||
uint32_t pcm_size = data_size * 2 * sizeof(short); // * channels
|
||||
if (pcm_size > expected_size)
|
||||
return NULL;
|
||||
|
||||
if (!is_id32be(0x08, sf, "WAVE"))
|
||||
return NULL;
|
||||
if (!is_id32be(0x0c, sf, "fmt "))
|
||||
return NULL;
|
||||
|
||||
fmt_size = read_s32le(0x10, sf);
|
||||
/* depending on the adp, fmt_size alternates between 0x10 and 0x12 */
|
||||
if (fmt_size < 0x10 || fmt_size > 0x12)
|
||||
goto fail;
|
||||
fmt_offset = 0x14;
|
||||
|
||||
if (read_s16le(fmt_offset + 0x00, sf) != 0x0001) /* PCM format */
|
||||
goto fail;
|
||||
channels = read_s16le(fmt_offset + 0x02, sf);
|
||||
if (channels != 1) /* not seen (decoder can't handle it) */
|
||||
goto fail;
|
||||
sample_rate = read_s32le(fmt_offset + 0x04, sf);
|
||||
if (read_s16le(fmt_offset + 0x0e, sf) != 16) /* PCM bit depth */
|
||||
goto fail;
|
||||
/* rest of fmt header is the usual header for 16-bit PCM wav files: bitrate, block size, and the like (see riff.c) */
|
||||
/* if fmt_size == 0x12 there may be is an additional s16 field that's always zero, but not always. */
|
||||
|
||||
/* next chunk is at fixed offset, regardless of fmt_size (fmt_size 0x12 with "data" at 0x24 is possible).
|
||||
* "data" has chunk size (does not match ADP size but original WAV) and "fact" chunk size 0x04 cut off) */
|
||||
if (!is_id32be(0x24, sf, "data") && !is_id32be(0x26, sf, "fact"))
|
||||
goto fail;
|
||||
|
||||
/* Ongagukan games using this format just read it by checking "ADP" extension in a provided file name,
|
||||
* and if extension is there they just read the reported "number of samples" and "sample_rate" vars
|
||||
* from RIFF WAVE "fmt " chunk based on an already-opened file with that same name.
|
||||
* and they don't even read RIFF chunks, they just pick these two vars and that's basically it. */
|
||||
|
||||
sound_is_adpcm = true;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_ONGAKUKAN_RIFF_ADP;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->codec_data = init_ongakukan_adp(sf, start_offset, data_size, sound_is_adpcm);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ONGAKUKAN_ADPCM;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_samples = ongakukan_adp_get_samples(vgmstream->codec_data);
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user