mirror of
https://github.com/holub/mame
synced 2025-05-22 13:48:55 +03:00
289 lines
7.3 KiB
C
289 lines
7.3 KiB
C
/*********************************************************************
|
|
|
|
wavfile.c
|
|
|
|
Format code for wave (*.wav) files
|
|
|
|
*********************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
#include "wavfile.h"
|
|
#include "cassimg.h"
|
|
|
|
static const char magic1[4] = { 'R', 'I', 'F', 'F' };
|
|
static const char magic2[4] = { 'W', 'A', 'V', 'E' };
|
|
static const char format_tag_id[4] = { 'f', 'm', 't', ' ' };
|
|
static const char data_tag_id[4] = { 'd', 'a', 't', 'a' };
|
|
|
|
#define WAV_FORMAT_PCM 1
|
|
|
|
|
|
|
|
static UINT32 get_leuint32(const void *ptr)
|
|
{
|
|
UINT32 value;
|
|
memcpy(&value, ptr, sizeof(value));
|
|
return LITTLE_ENDIANIZE_INT32(value);
|
|
}
|
|
|
|
|
|
|
|
static UINT16 get_leuint16(const void *ptr)
|
|
{
|
|
UINT16 value;
|
|
memcpy(&value, ptr, sizeof(value));
|
|
return LITTLE_ENDIANIZE_INT16(value);
|
|
}
|
|
|
|
|
|
|
|
static void put_leuint32(void *ptr, UINT32 value)
|
|
{
|
|
value = LITTLE_ENDIANIZE_INT32(value);
|
|
memcpy(ptr, &value, sizeof(value));
|
|
}
|
|
|
|
|
|
|
|
static void put_leuint16(void *ptr, UINT16 value)
|
|
{
|
|
value = LITTLE_ENDIANIZE_INT16(value);
|
|
memcpy(ptr, &value, sizeof(value));
|
|
}
|
|
|
|
|
|
|
|
static casserr_t wavfile_process(cassette_image *cassette, struct CassetteOptions *opts,
|
|
int read_waveform)
|
|
{
|
|
UINT8 file_header[12];
|
|
UINT8 tag_header[8];
|
|
UINT8 format_tag[16];
|
|
UINT32 stated_size;
|
|
UINT64 file_size;
|
|
UINT32 tag_size;
|
|
UINT32 tag_samples;
|
|
UINT64 offset;
|
|
int format_specified = FALSE;
|
|
|
|
UINT16 format_type = 0;
|
|
UINT32 bytes_per_second = 0;
|
|
// UINT16 block_align = 0;
|
|
int waveform_flags = 0;
|
|
|
|
/* read header */
|
|
cassette_image_read(cassette, file_header, 0, sizeof(file_header));
|
|
offset = sizeof(file_header);
|
|
|
|
/* check magic numbers */
|
|
if (memcmp(&file_header[0], magic1, 4))
|
|
return CASSETTE_ERROR_INVALIDIMAGE;
|
|
if (memcmp(&file_header[8], magic2, 4))
|
|
return CASSETTE_ERROR_INVALIDIMAGE;
|
|
|
|
/* read and sanity check size */
|
|
stated_size = get_leuint32(&file_header[4]) + 8;
|
|
file_size = cassette_image_size(cassette);
|
|
if (stated_size > file_size)
|
|
stated_size = (UINT32) file_size;
|
|
|
|
while(offset < stated_size)
|
|
{
|
|
cassette_image_read(cassette, tag_header, offset, sizeof(tag_header));
|
|
tag_size = get_leuint32(&tag_header[4]);
|
|
offset += sizeof(tag_header);
|
|
|
|
if (!memcmp(tag_header, format_tag_id, 4))
|
|
{
|
|
/* format tag */
|
|
if (format_specified || (tag_size < sizeof(format_tag)))
|
|
return CASSETTE_ERROR_INVALIDIMAGE;
|
|
format_specified = TRUE;
|
|
|
|
cassette_image_read(cassette, format_tag, offset, sizeof(format_tag));
|
|
|
|
format_type = get_leuint16(&format_tag[0]);
|
|
opts->channels = get_leuint16(&format_tag[2]);
|
|
opts->sample_frequency = get_leuint32(&format_tag[4]);
|
|
bytes_per_second = get_leuint32(&format_tag[8]);
|
|
// block_align = get_leuint16(&format_tag[12]);
|
|
opts->bits_per_sample = get_leuint16(&format_tag[14]);
|
|
|
|
if (format_type != WAV_FORMAT_PCM)
|
|
return CASSETTE_ERROR_INVALIDIMAGE;
|
|
if (opts->sample_frequency * opts->bits_per_sample * opts->channels / 8 != bytes_per_second)
|
|
return CASSETTE_ERROR_INVALIDIMAGE;
|
|
|
|
switch(opts->bits_per_sample)
|
|
{
|
|
case 8:
|
|
waveform_flags = CASSETTE_WAVEFORM_8BIT | CASSETTE_WAVEFORM_UNSIGNED; // 8-bits wav are stored unsigned
|
|
break;
|
|
case 16:
|
|
waveform_flags = CASSETTE_WAVEFORM_16BITLE;
|
|
break;
|
|
case 32:
|
|
waveform_flags = CASSETTE_WAVEFORM_32BITLE;
|
|
break;
|
|
default:
|
|
return CASSETTE_ERROR_INVALIDIMAGE;
|
|
}
|
|
}
|
|
else if (!memcmp(tag_header, data_tag_id, 4))
|
|
{
|
|
/* data tag */
|
|
if (!format_specified)
|
|
return CASSETTE_ERROR_INVALIDIMAGE;
|
|
|
|
if (read_waveform)
|
|
{
|
|
tag_samples = tag_size / (opts->bits_per_sample / 8) / opts->channels;
|
|
cassette_read_samples(cassette, opts->channels, 0.0, tag_samples / ((double) opts->sample_frequency),
|
|
tag_samples, offset, waveform_flags);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* ignore other tags */
|
|
}
|
|
offset += tag_size;
|
|
}
|
|
|
|
return CASSETTE_ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static casserr_t wavfile_identify(cassette_image *cassette, struct CassetteOptions *opts)
|
|
{
|
|
return wavfile_process(cassette, opts, FALSE);
|
|
}
|
|
|
|
|
|
|
|
static casserr_t wavfile_load(cassette_image *cassette)
|
|
{
|
|
struct CassetteOptions opts;
|
|
memset(&opts, 0, sizeof(opts));
|
|
return wavfile_process(cassette, &opts, TRUE);
|
|
}
|
|
|
|
|
|
|
|
static casserr_t wavfile_save(cassette_image *cassette, const struct CassetteInfo *info)
|
|
{
|
|
casserr_t err;
|
|
UINT8 consolidated_header[12 + 8 + 16 + 8];
|
|
UINT8 *header = &consolidated_header[0];
|
|
UINT8 *format_tag_header = &consolidated_header[12];
|
|
UINT8 *format_tag_data = &consolidated_header[12 + 8];
|
|
UINT8 *data_tag_header = &consolidated_header[12 + 8 + 16];
|
|
UINT32 file_size;
|
|
UINT32 bytes_per_second;
|
|
UINT16 bits_per_sample;
|
|
UINT32 data_size;
|
|
size_t bytes_per_sample = 2;
|
|
int waveform_flags = CASSETTE_WAVEFORM_16BITLE;
|
|
UINT16 block_align;
|
|
|
|
bits_per_sample = (UINT16) (bytes_per_sample * 8);
|
|
bytes_per_second = info->sample_frequency * bytes_per_sample * info->channels;
|
|
data_size = (UINT32) (info->sample_count * bytes_per_sample * info->channels);
|
|
file_size = data_size + sizeof(consolidated_header) - 8;
|
|
block_align = (UINT16) (bytes_per_sample * info->channels);
|
|
|
|
/* set up header */
|
|
memcpy(&header[0], magic1, 4);
|
|
memcpy(&header[8], magic2, 4);
|
|
put_leuint32(&header[4], file_size);
|
|
|
|
/* set up format tag */
|
|
memcpy(&format_tag_header[0], format_tag_id, 4);
|
|
put_leuint32(&format_tag_header[4], 16);
|
|
put_leuint16(&format_tag_data[0], WAV_FORMAT_PCM);
|
|
put_leuint16(&format_tag_data[2], info->channels);
|
|
put_leuint32(&format_tag_data[4], info->sample_frequency);
|
|
put_leuint32(&format_tag_data[8], bytes_per_second);
|
|
put_leuint16(&format_tag_data[12], block_align);
|
|
put_leuint16(&format_tag_data[14], bits_per_sample);
|
|
|
|
/* set up data tag */
|
|
memcpy(&data_tag_header[0], data_tag_id, 4);
|
|
put_leuint32(&data_tag_header[4], data_size);
|
|
|
|
/* write consolidated header */
|
|
cassette_image_write(cassette, consolidated_header, 0, sizeof(consolidated_header));
|
|
|
|
/* write out the actual data */
|
|
err = cassette_write_samples(cassette, info->channels, 0.0, info->sample_count
|
|
/ (double) info->sample_frequency, info->sample_count, sizeof(consolidated_header),
|
|
waveform_flags);
|
|
if (err)
|
|
return err;
|
|
|
|
return CASSETTE_ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
const struct CassetteFormat wavfile_format =
|
|
{
|
|
"wav",
|
|
wavfile_identify,
|
|
wavfile_load,
|
|
wavfile_save
|
|
};
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
wavfile_testload()
|
|
|
|
This is a hokey function used to test the cassette wave loading
|
|
system, specifically to test that when one loads a WAV file image
|
|
that the resulting info queried will be the same data in the WAV.
|
|
|
|
This code has already identified some rounding errors
|
|
*********************************************************************/
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
void wavfile_testload(const char *fname)
|
|
{
|
|
cassette_image *cassette;
|
|
FILE *f;
|
|
long offset;
|
|
int freq, samples, i;
|
|
INT32 cassamp;
|
|
INT16 wavsamp;
|
|
|
|
f = fopen(fname, "rb");
|
|
if (!f)
|
|
return;
|
|
|
|
if (cassette_open(f, &stdio_ioprocs, &wavfile_format, CASSETTE_FLAG_READONLY, &cassette))
|
|
{
|
|
fclose(f);
|
|
return;
|
|
}
|
|
|
|
offset = 44;
|
|
freq = 44100;
|
|
samples = 5667062;
|
|
|
|
for (i = 0; i < samples; i++)
|
|
{
|
|
cassette_get_sample(cassette, 0, i / (double) freq, 0.0, &cassamp);
|
|
|
|
fseek(f, offset + i * 2, SEEK_SET);
|
|
fread(&wavsamp, 1, 2, f);
|
|
assert(cassamp == (((UINT32) wavsamp) << 16));
|
|
}
|
|
|
|
cassette_close(cassette);
|
|
|
|
fclose(f);
|
|
}
|
|
#endif
|