summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/lib/formats/cassimg.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/lib/formats/cassimg.c')
-rw-r--r--trunk/src/lib/formats/cassimg.c1057
1 files changed, 1057 insertions, 0 deletions
diff --git a/trunk/src/lib/formats/cassimg.c b/trunk/src/lib/formats/cassimg.c
new file mode 100644
index 00000000000..f2c69a7c583
--- /dev/null
+++ b/trunk/src/lib/formats/cassimg.c
@@ -0,0 +1,1057 @@
+/*********************************************************************
+
+ cassimg.c
+
+ Cassette tape image abstraction code
+
+*********************************************************************/
+
+#include <assert.h>
+#include <string.h>
+
+#include "imageutl.h"
+#include "pool.h"
+#include "cassimg.h"
+
+
+/* debugging parameters */
+#define LOG_PUT_SAMPLES 0
+#define DUMP_CASSETTES 0
+
+#define SAMPLES_PER_BLOCK 0x40000
+#define CASSETTE_FLAG_DIRTY 0x10000
+
+
+CASSETTE_FORMATLIST_START(cassette_default_formats)
+CASSETTE_FORMATLIST_END
+
+
+
+/*********************************************************************
+ helper code
+*********************************************************************/
+
+static double map_double(double d, UINT64 low, UINT64 high, UINT64 value)
+{
+#if defined(_MSC_VER) && (_MSC_VER <= 1200)
+ /* casting unsigned __int64 to double is not supported on VC6 or before */
+ return d * (INT64)(value - low) / (INT64)(high - low);
+#else
+ return d * (value - low) / (high - low);
+#endif
+}
+
+
+
+static size_t waveform_bytes_per_sample(int waveform_flags)
+{
+ return (size_t) (1 << ((waveform_flags & 0x06) / 2));
+}
+
+
+
+/*********************************************************************
+ extrapolation and interpolation
+*********************************************************************/
+
+static INT32 extrapolate8(INT8 value)
+{
+ return ((INT32) value) << 24;
+}
+
+static INT32 extrapolate16(INT16 value)
+{
+ return ((INT32) value) << 16;
+}
+
+static INT8 interpolate8(INT32 value)
+{
+ return (INT8) (value >> 24);
+}
+
+static INT16 interpolate16(INT32 value)
+{
+ return (INT16) (value >> 16);
+}
+
+
+
+/*********************************************************************
+ initialization and termination
+*********************************************************************/
+
+static cassette_image *cassette_init(const struct CassetteFormat *format, void *file, const struct io_procs *procs, int flags)
+{
+ cassette_image *cassette;
+
+ cassette = (cassette_image *)malloc(sizeof(struct _cassette_image));
+ if (!cassette)
+ return NULL;
+
+ memset(cassette, 0, sizeof(*cassette));
+ cassette->format = format;
+ cassette->io.file = file;
+ cassette->io.procs = procs;
+ cassette->flags = flags;
+ cassette->pool = pool_alloc_lib(NULL);
+ return cassette;
+}
+
+
+
+static void cassette_finishinit(casserr_t err, cassette_image *cassette, cassette_image **outcassette)
+{
+ if (cassette && (err || !outcassette))
+ {
+ cassette_close(cassette);
+ cassette = NULL;
+ }
+ if (outcassette)
+ *outcassette = cassette;
+}
+
+
+
+static int good_format(const struct CassetteFormat *format, const char *extension, int flags)
+{
+ if (extension && !image_find_extension(format->extensions, extension))
+ return FALSE;
+ if (((flags & CASSETTE_FLAG_READONLY) == 0) && !format->save)
+ return FALSE;
+ return TRUE;
+}
+
+
+
+casserr_t cassette_open_choices(void *file, const struct io_procs *procs, const char *extension,
+ const struct CassetteFormat *const *formats, int flags, cassette_image **outcassette)
+{
+ casserr_t err;
+ cassette_image *cassette;
+ const struct CassetteFormat *format;
+ struct CassetteOptions opts = {0, };
+ int i;
+
+ /* if not specified, use the dummy arguments */
+ if (!formats)
+ formats = cassette_default_formats;
+
+ /* create the cassette object */
+ cassette = cassette_init(NULL, file, procs, flags);
+ if (!cassette)
+ {
+ err = CASSETTE_ERROR_OUTOFMEMORY;
+ goto done;
+ }
+
+ /* identify the image */
+ format = NULL;
+ for (i = 0; !format && formats[i]; i++)
+ {
+ if (good_format(formats[i], extension, flags))
+ {
+ format = formats[i];
+ memset(&opts, 0, sizeof(opts));
+ err = format->identify(cassette, &opts);
+ if (err == CASSETTE_ERROR_INVALIDIMAGE)
+ format = NULL;
+ else if (err)
+ goto done;
+ }
+ }
+
+ /* have we found a proper format */
+ if (!format)
+ {
+ err = CASSETTE_ERROR_INVALIDIMAGE;
+ goto done;
+ }
+ cassette->format = format;
+
+ /* read the options */
+ cassette->channels = opts.channels;
+ cassette->sample_frequency = opts.sample_frequency;
+
+ /* load the image */
+ err = format->load(cassette);
+ if (err)
+ goto done;
+
+ /* success */
+ cassette->flags &= ~CASSETTE_FLAG_DIRTY;
+ err = CASSETTE_ERROR_SUCCESS;
+
+done:
+ cassette_finishinit(err, cassette, outcassette);
+ return err;
+}
+
+
+
+casserr_t cassette_open(void *file, const struct io_procs *procs,
+ const struct CassetteFormat *format, int flags, cassette_image **outcassette)
+{
+ const struct CassetteFormat *formats[2];
+ formats[0] = format;
+ formats[1] = NULL;
+ return cassette_open_choices(file, procs, NULL, formats, flags, outcassette);
+}
+
+
+
+casserr_t cassette_create(void *file, const struct io_procs *procs, const struct CassetteFormat *format,
+ const struct CassetteOptions *opts, int flags, cassette_image **outcassette)
+{
+ casserr_t err;
+ cassette_image *cassette;
+ static const struct CassetteOptions default_options = { 1, 16, 44100 };
+
+ /* cannot create to a read only image */
+ if (flags & CASSETTE_FLAG_READONLY)
+ return CASSETTE_ERROR_INVALIDIMAGE;
+
+ /* is this a good format? */
+ if (!good_format(format, NULL, flags))
+ return CASSETTE_ERROR_INVALIDIMAGE;
+
+ /* normalize arguments */
+ if (!opts)
+ opts = &default_options;
+
+ /* create the cassette object */
+ cassette = cassette_init(format, file, procs, flags);
+ if (!cassette)
+ {
+ err = CASSETTE_ERROR_OUTOFMEMORY;
+ goto done;
+ }
+
+ /* read the options */
+ cassette->channels = opts->channels;
+ cassette->sample_frequency = opts->sample_frequency;
+
+ err = CASSETTE_ERROR_SUCCESS;
+
+done:
+ cassette_finishinit(err, cassette, outcassette);
+ return err;
+}
+
+
+
+static casserr_t cassette_perform_save(cassette_image *cassette)
+{
+ struct CassetteInfo info;
+ cassette_get_info(cassette, &info);
+ return cassette->format->save(cassette, &info);
+}
+
+
+
+casserr_t cassette_save(cassette_image *cassette)
+{
+ casserr_t err;
+
+ if (!cassette->format || !cassette->format->save)
+ return CASSETTE_ERROR_UNSUPPORTED;
+
+ err = cassette_perform_save(cassette);
+ if (err)
+ return err;
+
+ cassette->flags &= ~CASSETTE_FLAG_DIRTY;
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+void cassette_get_info(cassette_image *cassette, struct CassetteInfo *info)
+{
+ memset(info, 0, sizeof(*info));
+ info->channels = cassette->channels;
+ info->sample_count = cassette->sample_count;
+ info->sample_frequency = cassette->sample_frequency;
+ info->bits_per_sample = (int) waveform_bytes_per_sample(cassette->flags) * 8;
+}
+
+
+
+void cassette_close(cassette_image *cassette)
+{
+ if (cassette)
+ {
+ if ((cassette->flags & CASSETTE_FLAG_DIRTY) && (cassette->flags & CASSETTE_FLAG_SAVEONEXIT))
+ cassette_save(cassette);
+ pool_free_lib(cassette->pool);
+ free(cassette);
+ }
+}
+
+
+
+void cassette_change(cassette_image *cassette, void *file, const struct io_procs *procs, const struct CassetteFormat *format, int flags)
+{
+ if ((flags & CASSETTE_FLAG_READONLY) == 0)
+ flags |= CASSETTE_FLAG_DIRTY;
+ cassette->io.file = file;
+ cassette->io.procs = procs;
+ cassette->format = format;
+ cassette->flags = flags;
+}
+
+
+
+/*********************************************************************
+ calls for accessing the raw cassette image
+*********************************************************************/
+
+void cassette_image_read(cassette_image *cassette, void *buffer, UINT64 offset, size_t length)
+{
+ io_generic_read(&cassette->io, buffer, offset, length);
+}
+
+
+
+void cassette_image_write(cassette_image *cassette, const void *buffer, UINT64 offset, size_t length)
+{
+ io_generic_write(&cassette->io, buffer, offset, length);
+}
+
+
+
+UINT64 cassette_image_size(cassette_image *cassette)
+{
+ return io_generic_size(&cassette->io);
+}
+
+
+
+/*********************************************************************
+ waveform accesses
+*********************************************************************/
+
+struct manipulation_ranges
+{
+ int channel_first;
+ int channel_last;
+ size_t sample_first;
+ size_t sample_last;
+};
+
+
+
+static size_t my_round(double d)
+{
+ size_t result;
+ d += 0.5;
+ result = (size_t) d;
+ return result;
+}
+
+
+
+static casserr_t compute_manipulation_ranges(cassette_image *cassette, int channel,
+ double time_index, double sample_period, struct manipulation_ranges *ranges)
+{
+ if (channel < 0)
+ {
+ ranges->channel_first = 0;
+ ranges->channel_last = cassette->channels - 1;
+ }
+ else
+ {
+ ranges->channel_first = channel;
+ ranges->channel_last = channel;
+ }
+
+ ranges->sample_first = my_round(time_index * cassette->sample_frequency);
+ ranges->sample_last = my_round((time_index + sample_period) * cassette->sample_frequency);
+
+ if (ranges->sample_last > ranges->sample_first)
+ ranges->sample_last--;
+
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+static casserr_t lookup_sample(cassette_image *cassette, int channel, size_t sample, int allocate, INT32 **ptr)
+{
+ size_t sample_block;
+ size_t sample_index;
+ size_t sample_size;
+ size_t new_block_count;
+ size_t new_block_sample_count;
+ INT32 *new_block;
+ struct sample_block *new_blocks;
+ struct sample_block *block;
+
+ *ptr = NULL;
+ sample_block = (sample / SAMPLES_PER_BLOCK) * cassette->channels + channel;
+ sample_index = sample % SAMPLES_PER_BLOCK;
+ sample_size = sizeof(block->block[0]);
+
+ /* is this block beyond the edge of our waveform? */
+ if (sample_block >= cassette->block_count)
+ {
+ if (!allocate)
+ return CASSETTE_ERROR_SUCCESS;
+
+ /* allocate new blocks */
+ new_block_count = sample_block + 1;
+ new_blocks = (struct sample_block *)pool_realloc_lib(cassette->pool, cassette->blocks, new_block_count * sizeof(cassette->blocks[0]));
+ if (!new_blocks)
+ return CASSETTE_ERROR_OUTOFMEMORY;
+
+ cassette->blocks = new_blocks;
+ memset(&cassette->blocks[cassette->block_count], 0, (new_block_count - cassette->block_count) * sizeof(cassette->blocks[0]));
+ cassette->block_count = new_block_count;
+ }
+
+ block = &cassette->blocks[sample_block];
+
+ /* is this sample access off the current block? */
+ if (sample_index >= block->sample_count)
+ {
+ if (!allocate)
+ return CASSETTE_ERROR_SUCCESS;
+
+ new_block_sample_count = SAMPLES_PER_BLOCK;
+
+ new_block = (INT32*)pool_realloc_lib(cassette->pool, block->block, new_block_sample_count * sample_size);
+ if (!new_block)
+ return CASSETTE_ERROR_OUTOFMEMORY;
+
+ block->block = new_block;
+ memset(&block->block[block->sample_count], 0, (new_block_sample_count - block->sample_count) * sample_size);
+ block->sample_count = new_block_sample_count;
+ }
+
+ *ptr = &block->block[sample_index];
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+/*********************************************************************
+ waveform accesses
+*********************************************************************/
+
+casserr_t cassette_get_samples(cassette_image *cassette, int channel,
+ double time_index, double sample_period, size_t sample_count, size_t sample_bytes,
+ void *samples, int waveform_flags)
+{
+ casserr_t err;
+ struct manipulation_ranges ranges;
+ size_t sample_index;
+ size_t cassette_sample_index;
+ UINT8 *dest_ptr;
+ const INT32 *source_ptr;
+ double d;
+ INT16 word;
+ INT32 dword;
+ INT64 sum;
+
+ assert(cassette);
+
+ err = compute_manipulation_ranges(cassette, channel, time_index, sample_period, &ranges);
+ if (err)
+ return err;
+
+ for (sample_index = 0; sample_index < sample_count; sample_index++)
+ {
+ sum = 0;
+
+ for (channel = ranges.channel_first; channel <= ranges.channel_last; channel++)
+ {
+ /* find the sample that we are putting */
+ d = map_double(ranges.sample_last + 1 - ranges.sample_first, 0, sample_count, sample_index) + ranges.sample_first;
+ cassette_sample_index = (size_t) d;
+ err = lookup_sample(cassette, channel, cassette_sample_index, TRUE, (INT32 **) &source_ptr);
+ if (err)
+ return err;
+
+ sum += *source_ptr;
+ }
+
+ /* average out the samples */
+ sum /= (ranges.channel_last + 1 - ranges.channel_first);
+
+ /* and write out the result */
+ dest_ptr = (UINT8*)samples;
+ dest_ptr += waveform_bytes_per_sample(waveform_flags) * sample_index;
+ switch(waveform_bytes_per_sample(waveform_flags))
+ {
+ case 1:
+ *((INT8 *) dest_ptr) = interpolate8(sum);
+ break;
+ case 2:
+ word = interpolate16(sum);
+ if (waveform_flags & CASSETTE_WAVEFORM_ENDIAN_FLIP)
+ word = FLIPENDIAN_INT16(word);
+ *((INT16 *) dest_ptr) = word;
+ break;
+ case 4:
+ dword = sum;
+ if (waveform_flags & CASSETTE_WAVEFORM_ENDIAN_FLIP)
+ dword = FLIPENDIAN_INT32(dword);
+ *((INT32 *) dest_ptr) = dword;
+ break;
+ }
+ }
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+casserr_t cassette_put_samples(cassette_image *cassette, int channel,
+ double time_index, double sample_period, size_t sample_count, size_t sample_bytes,
+ const void *samples, int waveform_flags)
+{
+ casserr_t err;
+ struct manipulation_ranges ranges;
+ size_t sample_index;
+ INT32 *dest_ptr;
+ INT32 dest_value;
+ INT16 word;
+ INT32 dword;
+ const UINT8 *source_ptr;
+ double d;
+
+ if (!cassette)
+ return CASSETTE_ERROR_SUCCESS;
+
+ if (sample_period == 0)
+ return CASSETTE_ERROR_SUCCESS;
+
+ err = compute_manipulation_ranges(cassette, channel, time_index, sample_period, &ranges);
+ if (err)
+ return err;
+
+ if (cassette->sample_count < ranges.sample_last+1)
+ cassette->sample_count = ranges.sample_last + 1;
+ cassette->flags |= CASSETTE_FLAG_DIRTY;
+
+ if (LOG_PUT_SAMPLES)
+ {
+ LOG_FORMATS("cassette_put_samples(): Putting samples TIME=[%2.6g..%2.6g] INDEX=[%i..%i]\n",
+ time_index, time_index + sample_period,
+ (int)ranges.sample_first, (int)ranges.sample_last);
+ }
+
+ for (sample_index = ranges.sample_first; sample_index <= ranges.sample_last; sample_index++)
+ {
+ /* figure out the source pointer */
+ d = map_double(sample_count, ranges.sample_first, ranges.sample_last + 1, sample_index);
+ source_ptr = (const UINT8*)samples;
+ source_ptr += ((size_t) d) * sample_bytes;
+
+ /* compute the value that we are writing */
+ switch(waveform_bytes_per_sample(waveform_flags)) {
+ case 1:
+ dest_value = extrapolate8(*((INT8 *) source_ptr));
+ break;
+ case 2:
+ word = *((INT16 *) source_ptr);
+ if (waveform_flags & CASSETTE_WAVEFORM_ENDIAN_FLIP)
+ word = FLIPENDIAN_INT16(word);
+ dest_value = extrapolate16(word);
+ break;
+ case 4:
+ dword = *((INT32 *) source_ptr);
+ if (waveform_flags & CASSETTE_WAVEFORM_ENDIAN_FLIP)
+ dword = FLIPENDIAN_INT32(dword);
+ dest_value = dword;
+ break;
+ default:
+ return CASSETTE_ERROR_INTERNAL;
+ }
+
+ for (channel = ranges.channel_first; channel <= ranges.channel_last; channel++)
+ {
+ /* find the sample that we are putting */
+ err = lookup_sample(cassette, channel, sample_index, TRUE, &dest_ptr);
+ if (err)
+ return err;
+ *dest_ptr = dest_value;
+ }
+ }
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+casserr_t cassette_get_sample(cassette_image *cassette, int channel,
+ double time_index, double sample_period, INT32 *sample)
+{
+ return cassette_get_samples(cassette, channel, time_index,
+ sample_period, 1, 0, sample, CASSETTE_WAVEFORM_32BIT);
+}
+
+
+
+casserr_t cassette_put_sample(cassette_image *cassette, int channel,
+ double time_index, double sample_period, INT32 sample)
+{
+ return cassette_put_samples(cassette, channel, time_index,
+ sample_period, 1, 0, &sample, CASSETTE_WAVEFORM_32BIT);
+}
+
+
+
+/*********************************************************************
+ waveform accesses to/from the raw image
+*********************************************************************/
+
+casserr_t cassette_read_samples(cassette_image *cassette, int channels, double time_index,
+ double sample_period, size_t sample_count, UINT64 offset, int waveform_flags)
+{
+ casserr_t err;
+ size_t chunk_sample_count;
+ size_t bytes_per_sample;
+ size_t sample_bytes;
+ size_t samples_loaded = 0;
+ double chunk_time_index;
+ double chunk_sample_period;
+ int channel;
+ UINT8 buffer[8192];
+
+ bytes_per_sample = waveform_bytes_per_sample(waveform_flags);
+ sample_bytes = bytes_per_sample * channels;
+
+ while(samples_loaded < sample_count)
+ {
+ chunk_sample_count = MIN(sizeof(buffer) / sample_bytes, (sample_count - samples_loaded));
+ chunk_sample_period = map_double(sample_period, 0, sample_count, chunk_sample_count);
+ chunk_time_index = time_index + map_double(sample_period, 0, sample_count, samples_loaded);
+
+ cassette_image_read(cassette, buffer, offset, chunk_sample_count * sample_bytes);
+
+ for (channel = 0; channel < channels; channel++)
+ {
+ err = cassette_put_samples(cassette, channel, chunk_time_index, chunk_sample_period,
+ chunk_sample_count, sample_bytes, &buffer[channel * bytes_per_sample], waveform_flags);
+ if (err)
+ return err;
+ }
+
+ offset += chunk_sample_count * sample_bytes;
+ samples_loaded += chunk_sample_count;
+ }
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+casserr_t cassette_write_samples(cassette_image *cassette, int channels, double time_index,
+ double sample_period, size_t sample_count, UINT64 offset, int waveform_flags)
+{
+ casserr_t err;
+ size_t chunk_sample_count;
+ size_t bytes_per_sample;
+ size_t sample_bytes;
+ size_t samples_saved = 0;
+ double chunk_time_index;
+ double chunk_sample_period;
+ int channel;
+ UINT8 buffer[8192];
+
+ bytes_per_sample = waveform_bytes_per_sample(waveform_flags);
+ sample_bytes = bytes_per_sample * channels;
+
+ while(samples_saved < sample_count)
+ {
+ chunk_sample_count = MIN(sizeof(buffer) / sample_bytes, (sample_count - samples_saved));
+ chunk_sample_period = map_double(sample_period, 0, sample_count, chunk_sample_count);
+ chunk_time_index = time_index + map_double(sample_period, 0, sample_count, samples_saved);
+
+ for (channel = 0; channel < channels; channel++)
+ {
+ err = cassette_get_samples(cassette, channel, chunk_time_index, chunk_sample_period,
+ chunk_sample_count, sample_bytes, &buffer[channel * bytes_per_sample], waveform_flags);
+ if (err)
+ return err;
+ }
+
+ cassette_image_write(cassette, buffer, offset, chunk_sample_count * sample_bytes);
+
+ offset += chunk_sample_count * sample_bytes;
+ samples_saved += chunk_sample_count;
+ }
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+/*********************************************************************
+ waveform accesses to/from the raw image
+*********************************************************************/
+
+static const INT8 *choose_wave(const struct CassetteModulation *modulation, size_t *wave_bytes_length)
+{
+ static const INT8 square_wave[] = { -128, 127 };
+ static const INT8 sine_wave[] = { 0, 48, 89, 117, 127, 117, 89, 48, 0, -48, -89, -117, -127, -117, -89, -48 };
+
+ if (modulation->flags & CASSETTE_MODULATION_SINEWAVE)
+ {
+ *wave_bytes_length = ARRAY_LENGTH(sine_wave);
+ return sine_wave;
+ }
+ else
+ {
+ *wave_bytes_length = ARRAY_LENGTH(square_wave);
+ return square_wave;
+ }
+}
+
+
+
+casserr_t cassette_modulation_identify(cassette_image *cassette, const struct CassetteModulation *modulation,
+ struct CassetteOptions *opts)
+{
+ size_t wave_bytes_length;
+ choose_wave(modulation, &wave_bytes_length);
+ opts->bits_per_sample = 8;
+ opts->channels = 1;
+ opts->sample_frequency = (UINT32) (MAX(modulation->zero_frequency_high, modulation->one_frequency_high) * wave_bytes_length * 2);
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+casserr_t cassette_put_modulated_data(cassette_image *cassette, int channel, double time_index,
+ const void *data, size_t data_length, const struct CassetteModulation *modulation,
+ double *time_displacement)
+{
+ casserr_t err;
+ const UINT8 *data_bytes = (const UINT8 *)data;
+ const INT8 *wave_bytes;
+ size_t wave_bytes_length;
+ double total_displacement = 0.0;
+ double pulse_period;
+ double pulse_frequency;
+ UINT8 b;
+ int i;
+
+ wave_bytes = choose_wave(modulation, &wave_bytes_length);
+
+ while(data_length--)
+ {
+ b = *(data_bytes++);
+ for (i = 0; i < 8; i++)
+ {
+ pulse_frequency = (b & (1 << i)) ? modulation->one_frequency_cannonical : modulation->zero_frequency_cannonical;
+ pulse_period = 1 / pulse_frequency;
+ err = cassette_put_samples(cassette, 0, time_index, pulse_period, wave_bytes_length, 1, wave_bytes, CASSETTE_WAVEFORM_8BIT);
+ if (err)
+ goto done;
+ time_index += pulse_period;
+ total_displacement += pulse_period;
+ }
+ }
+ err = CASSETTE_ERROR_SUCCESS;
+
+done:
+ if (time_displacement)
+ *time_displacement = total_displacement;
+ return err;
+}
+
+
+
+casserr_t cassette_put_modulated_filler(cassette_image *cassette, int channel, double time_index,
+ UINT8 filler, size_t filler_length, const struct CassetteModulation *modulation,
+ double *time_displacement)
+{
+ casserr_t err;
+ double delta;
+ double total_displacement = 0.0;
+
+ while(filler_length--)
+ {
+ err = cassette_put_modulated_data(cassette, channel, time_index, &filler, 1, modulation, &delta);
+ if (err)
+ return err;
+ total_displacement += delta;
+ time_index += delta;
+ }
+
+ if (time_displacement)
+ *time_displacement = total_displacement;
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+casserr_t cassette_read_modulated_data(cassette_image *cassette, int channel, double time_index,
+ UINT64 offset, UINT64 length, const struct CassetteModulation *modulation,
+ double *time_displacement)
+{
+ casserr_t err;
+ UINT8 buffer_stack[1024];
+ UINT8 *buffer;
+ UINT8 *alloc_buffer = NULL;
+ double delta;
+ double total_displacement = 0.0;
+ size_t this_length;
+ size_t buffer_length;
+
+ if (length <= sizeof(buffer_stack))
+ {
+ buffer = buffer_stack;
+ buffer_length = sizeof(buffer_stack);
+ }
+ else
+ {
+ buffer_length = MIN(length, 100000);
+ alloc_buffer = (UINT8*)malloc(buffer_length);
+ if (!alloc_buffer)
+ {
+ err = CASSETTE_ERROR_OUTOFMEMORY;
+ goto done;
+ }
+ buffer = alloc_buffer;
+ }
+
+ while(length > 0)
+ {
+ this_length = (size_t) MIN(length, buffer_length);
+ cassette_image_read(cassette, buffer, offset, this_length);
+
+ err = cassette_put_modulated_data(cassette, channel, time_index, buffer, this_length, modulation, &delta);
+ if (err)
+ goto done;
+ total_displacement += delta;
+ time_index += delta;
+ length -= this_length;
+ }
+
+ if (time_displacement)
+ *time_displacement = total_displacement;
+ err = CASSETTE_ERROR_SUCCESS;
+
+done:
+ if (alloc_buffer)
+ free(alloc_buffer);
+ return err;
+}
+
+
+
+casserr_t cassette_put_modulated_data_bit(cassette_image *cassette, int channel, double time_index,
+ UINT8 data, const struct CassetteModulation *modulation,
+ double *time_displacement)
+{
+ casserr_t err;
+ const INT8 *wave_bytes;
+ size_t wave_bytes_length;
+ double total_displacement = 0.0;
+ double pulse_period;
+ double pulse_frequency;
+
+ wave_bytes = choose_wave(modulation, &wave_bytes_length);
+
+ pulse_frequency = (data) ? modulation->one_frequency_cannonical : modulation->zero_frequency_cannonical;
+ pulse_period = 1 / pulse_frequency;
+ err = cassette_put_samples(cassette, 0, time_index, pulse_period, wave_bytes_length, 1, wave_bytes, CASSETTE_WAVEFORM_8BIT);
+ if (err)
+ goto done;
+ time_index += pulse_period;
+ total_displacement += pulse_period;
+
+ err = CASSETTE_ERROR_SUCCESS;
+
+done:
+ if (time_displacement)
+ *time_displacement = total_displacement;
+ return err;
+}
+
+
+
+/*********************************************************************
+ waveform accesses to/from the raw image
+*********************************************************************/
+
+casserr_t cassette_legacy_identify(cassette_image *cassette, struct CassetteOptions *opts,
+ const struct CassetteLegacyWaveFiller *legacy_args)
+{
+ opts->channels = 1;
+ opts->bits_per_sample = 16;
+ opts->sample_frequency = legacy_args->sample_frequency;
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+
+
+casserr_t cassette_legacy_construct(cassette_image *cassette,
+ const struct CassetteLegacyWaveFiller *legacy_args)
+{
+ casserr_t err;
+ int length;
+ int sample_count;
+ void *bytes = NULL;
+ void *chunk = NULL;
+ INT16 *samples = NULL;
+ int pos = 0;
+ UINT64 offset = 0;
+ UINT64 size;
+ struct CassetteLegacyWaveFiller args;
+
+ /* sanity check the args */
+ assert(legacy_args->header_samples >= -1);
+ assert(legacy_args->trailer_samples >= 0);
+ assert(legacy_args->fill_wave);
+
+ size = cassette_image_size(cassette);
+
+ /* normalize the args */
+ args = *legacy_args;
+ if (args.chunk_size == 0)
+ args.chunk_size = 1;
+ else if (args.chunk_size < 0)
+ args.chunk_size = cassette_image_size(cassette);
+ if (args.sample_frequency == 0)
+ args.sample_frequency = 11025;
+
+ /* allocate a buffer for the binary data */
+ chunk = malloc(args.chunk_size);
+ if (!chunk)
+ {
+ err = CASSETTE_ERROR_OUTOFMEMORY;
+ goto done;
+ }
+
+ /* determine number of samples */
+ if (args.chunk_sample_calc)
+ {
+ if (size > 0x7FFFFFFF)
+ {
+ err = CASSETTE_ERROR_OUTOFMEMORY;
+ goto done;
+ }
+
+ bytes = malloc(size);
+ if (!bytes)
+ {
+ err = CASSETTE_ERROR_OUTOFMEMORY;
+ goto done;
+ }
+ cassette_image_read(cassette, bytes, 0, size);
+ sample_count = args.chunk_sample_calc((const UINT8*)bytes, (int) size);
+
+ if (args.header_samples < 0)
+ args.header_samples = sample_count;
+ }
+ else
+ {
+ sample_count = ((size + args.chunk_size - 1) / args.chunk_size)
+ * args.chunk_samples;
+ }
+ sample_count += args.header_samples + args.trailer_samples;
+
+ /* allocate a buffer for the completed samples */
+ samples = (INT16*)malloc(sample_count * sizeof(*samples));
+ if (!samples)
+ {
+ err = CASSETTE_ERROR_OUTOFMEMORY;
+ goto done;
+ }
+
+ /* if there has to be a header */
+ if (args.header_samples > 0)
+ {
+ length = args.fill_wave(samples + pos, sample_count - pos, CODE_HEADER);
+ if (length < 0)
+ {
+ err = CASSETTE_ERROR_INVALIDIMAGE;
+ goto done;
+ }
+ pos += length;
+ }
+
+ /* convert the file data to samples */
+ while((pos < sample_count) && (offset < size))
+ {
+ cassette_image_read(cassette, chunk, offset, args.chunk_size);
+ offset += args.chunk_size;
+
+ length = args.fill_wave(samples + pos, sample_count - pos, (UINT8*)chunk);
+ if (length < 0)
+ {
+ err = CASSETTE_ERROR_INVALIDIMAGE;
+ goto done;
+ }
+ pos += length;
+ if (length == 0)
+ break;
+ }
+
+ /* if there has to be a trailer */
+ if (args.trailer_samples > 0)
+ {
+ length = args.fill_wave(samples + pos, sample_count - pos, CODE_TRAILER);
+ if (length < 0)
+ {
+ err = CASSETTE_ERROR_INVALIDIMAGE;
+ goto done;
+ }
+ pos += length;
+ }
+
+ /* specify the wave */
+ err = cassette_put_samples(cassette, 0, 0.0, ((double) pos) / args.sample_frequency,
+ pos, 2, samples, CASSETTE_WAVEFORM_16BIT);
+ if (err)
+ goto done;
+
+ /* success! */
+ err = CASSETTE_ERROR_SUCCESS;
+
+#if DUMP_CASSETTES
+ cassette_dump(cassette, "C:\\TEMP\\CASDUMP.WAV");
+#endif
+
+done:
+ if (samples)
+ free(samples);
+ if (chunk)
+ free(chunk);
+ if (bytes)
+ free(bytes);
+ return err;
+}
+
+
+
+/*********************************************************************
+ cassette_dump
+
+ A debugging call to dump a casette image to a disk based wave file
+*********************************************************************/
+
+void cassette_dump(cassette_image *image, const char *filename)
+{
+ FILE *f;
+ struct io_generic saved_io;
+ const struct CassetteFormat *saved_format;
+
+ f = fopen(filename, "wb");
+ if (!f)
+ return;
+
+ memcpy(&saved_io, &image->io, sizeof(saved_io));
+ saved_format = image->format;
+
+ image->io.file = f;
+ image->io.procs = &stdio_ioprocs_noclose;
+ image->format = &wavfile_format;
+ cassette_perform_save(image);
+
+ memcpy(&image->io, &saved_io, sizeof(saved_io));
+ image->format = saved_format;
+
+ fclose(f);
+}
+
+