Fix some Ongakukan .adp + cleanup

This commit is contained in:
bnnm 2024-07-18 22:43:13 +02:00
parent b916d2685e
commit 33f01354e6
4 changed files with 409 additions and 439 deletions

View File

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

View File

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

View File

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

View File

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