summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/lib/formats/wavfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/lib/formats/wavfile.c')
-rw-r--r--trunk/src/lib/formats/wavfile.c291
1 files changed, 291 insertions, 0 deletions
diff --git a/trunk/src/lib/formats/wavfile.c b/trunk/src/lib/formats/wavfile.c
new file mode 100644
index 00000000000..da3615a9382
--- /dev/null
+++ b/trunk/src/lib/formats/wavfile.c
@@ -0,0 +1,291 @@
+/*********************************************************************
+
+ 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;
+ 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
+
+
+