💾 Archived View for uscoffings.net › retro-computing › systems › TI994a › x99tape › x99tape_src › wa… captured on 2023-03-20 at 21:54:23.

View Raw

More Information

⬅️ Previous capture (2022-06-04)

-=-=-=-=-=-=-

/*
	Portable TI99 tape encoder/decoder

	wave.c: Code to read and write PCM WAVE files

	Raphael Nabet 2002


#include <math.h>
#include <stdio.h>

#include "common.h"
#include "my_int.h"

#include "wave.h"

/* typedef for riff 32-bit tag, intel word (16 bits) and intel dword (32 bits) */
typedef uint_fast32_t riff_tag_t;
typedef uint_fast16_t intel_word_t;
typedef uint_fast32_t intel_dword_t;

/* defines for supported tags in WAVE RIFF file */
enum
{
	tag_RIFF = 0x52494646UL,	/* 'RIFF' */
	tag_WAVE = 0x57415645UL,	/* 'WAVE' */
	tag_fmt  = 0x666D7420UL,	/* 'fmt ' */
	tag_data = 0x64617461UL		/* 'data' */
};

/* static prototypes */
static error_t read_wave_intel_word(FILE *handle, intel_word_t *reply);
static error_t read_wave_intel_dword(FILE *handle, intel_dword_t *reply);
static error_t read_wave_tag(FILE *handle, riff_tag_t *reply);

/* set to 1 to enable a simple robustness test: add random white noise to signal */
#define TEST 0

#if TEST

#include <stdlib.h>

static const double noise_level = .35;

#if 1
static double my_rand(void)
{
	return (rand()*2-RAND_MAX)/(double)RAND_MAX;
}
#else
static double my_rand(void)
{
	double x, y, norm_2;

	do
	{
		x = (rand()*2-RAND_MAX)/(double)RAND_MAX;
		y = (rand()*2-RAND_MAX)/(double)RAND_MAX;
		norm_2 = x*x + y*y;
	}
	while (norm_2 == 0.) && (norm_2 > 1.);

	
}
#endif


#endif


/*
	Parse WAVE header and fill the wave_file structure accordingly, so that
	read_wave_sample() and seek_wave_sample() can be called afterwards.

	handle: stdc stream pointing to start of WAVE file
	wave_file: pointer to the wave_file_t to fill

	Returns error code (no_error or incorrect_parameters, bad_wave_format,
	unsupported_wave_format)

error_t open_wave_in(FILE *handle, wave_file_in_t *wave_file)
{
	error_t error;
	riff_tag_t tag;
	intel_word_t word;
	intel_dword_t dword;
	unsigned long field_len;


	/* set up handle */
	if (! handle)
		return invalid_parameters;
	wave_file->handle = handle;


	/* read RIFF header*/
	/* read 'RIFF' tag */
	if ((error = read_wave_tag(handle, & tag)) != no_error)
		return error;
	if (tag != tag_RIFF)
		return bad_wave_format;

	/* read total len field */
	if ((error = read_wave_intel_dword(handle, & dword)) != no_error)
		return error;
	/*wave_file->len = dword;*/

	/* read type tag ('WAVE') */
	if ((error = read_wave_tag(handle, & tag)) != no_error)
		return error;
	if (tag != tag_WAVE)
		return bad_wave_format;


	/* now read variable number of tagged fields */
	do
	{
		/* read field tag */
		if ((error = read_wave_tag(handle, & tag)) != no_error)
			return error;

		/* read field len */
		if ((error = read_wave_intel_dword(handle, & dword)) != no_error)
			return error;
		field_len = dword;

		switch (tag)
		{
		case tag_fmt:	/* fmt field */
			/* Check field length once and for all.  Length should be at least 14 bytes in
			the general case, and 16 in the case of PCM encoding. */
			if (field_len < 16)
				return (field_len < 14) ? bad_wave_format : unsupported_wave_format;

			/***Fields common to all encoding***/
			/* read format tag */
			if ((error = read_wave_intel_word(handle, & word)) != no_error)
				return error;
			if (word != 1)
				return unsupported_wave_format;	/* only PCM is supported */
			/*wave_file->formatTag = word;*/
			/* read number of channels */
			if ((error = read_wave_intel_word(handle, & word)) != no_error)
				return error;
			wave_file->channels = word;
			/* read sampling rate */
			if ((error = read_wave_intel_dword(handle, & dword)) != no_error)
				return error;
			wave_file->samplesPerSec = dword;
			/* read byte rate */
			if ((error = read_wave_intel_dword(handle, & dword)) != no_error)
				return error;
			/*wave_file->avgBytesPerSec = dword;*/
			/* read block alignment */
			if ((error = read_wave_intel_word(handle, & word)) != no_error)
				return error;
			wave_file->blockAlign = word;

			/***Field specific to PCM***/
			/* read bits per sample */
			if ((error = read_wave_intel_word(handle, & word)) != no_error)
				return error;
			wave_file->bitsPerSample = word;

			/* seek past end of field if necessary */
			if (field_len>16)
				fseek(handle, field_len-16, SEEK_CUR);

			/* Compute a few constants */
			if (wave_file->blockAlign % wave_file->channels)
				return bad_wave_format;		/* This is what Microsoft says */

			wave_file->sample_padding = (wave_file->blockAlign/wave_file->channels) - ((wave_file->bitsPerSample+7) >> 3);

			wave_file->multiplier = 1. / (pow(2, wave_file->bitsPerSample-1) * wave_file->channels);

			break;

		case tag_data:	/* data field: wave data follows */
			/*wave_file->data_len = field_len;*/
			wave_file->sample_len = field_len / wave_file->blockAlign;

			wave_file->sample_pos = 0;				/* currently pointing to first sample in file */
			wave_file->data_offset = ftell(handle);	/* remember data offset in file */

			return no_error;	/* only way we can exit OK from function */

		default:	/* unknown */
			/* Well, we don't mind extra fields, let's just skip it */
			fseek(handle, field_len, SEEK_CUR);
			break;
		}
	} while (1);
}

/*
	read_wave_sample: read a sample from a wave_file_in_t

	wave_file: wave_file structure
	reply: sample (normalized to fit in [-1,1])

	Returns error code (no_error, eof_error or read_error)

error_t read_wave_sample(wave_file_in_t *wave_file, float *reply)
{
	int bit;
	int channel;

	int ch;
	intmax_t sample_buf;
	float buf;

	/* check we have not reached end of data field */
	if (wave_file->sample_pos >= wave_file->sample_len)
		return eof_error;

	buf = 0;
	for (channel=0; channel<wave_file->channels; channel++)
	{
		sample_buf = 0;
		/* skip pad bytes */
		if (wave_file->sample_padding)
			fseek(wave_file->handle, wave_file->sample_padding, SEEK_CUR);
		/* read ceil(wave_file->bitsPerSample/8) bits */
		for (bit=0; bit<wave_file->bitsPerSample; bit+=8)
		{
			ch = getc(wave_file->handle);
			if (ch == EOF)
				return read_error;

			sample_buf |= ((uintmax_t) (ch & 0xff)) << bit;
		}
		/* shift out undefined bits */
		if (bit > wave_file->bitsPerSample)
			sample_buf >>= bit-wave_file->bitsPerSample;
		if (wave_file->bitsPerSample <= 8)
			/* convert to signed if unsigned */
			sample_buf -= 1 << (wave_file->bitsPerSample-1);
		else
		{
			/* extend sign bit */
			if (sample_buf & ((intmax_t)1 << (wave_file->bitsPerSample-1)))
				sample_buf |= ~ (((intmax_t)1 << wave_file->bitsPerSample)-1);
		}

		/* mix with previous channels */
		buf += sample_buf;
	}
	/* normalize and save reply */
	*reply = buf*wave_file->multiplier;

#if TEST
	/* add white noise (debug test) */
	*reply = *reply*(1-noise_level)+noise_level*my_rand();
#endif

	wave_file->sample_pos++;

	return no_error;
}

/*
	seek_wave_sample: seek to a given sample in a wave_file

	wave_file: wave_file structure
	sample_position: sample index

	Returns error code (always no_error)

/*error_t seek_wave_sample(wave_file_in_t *wave_file, uint_fast32_t sample_pos)
{
	fseek(wave_file->handle, wave_file->data_offset+sample_pos*wave_file->blockAlign, SEEK_SET);
	wave_file->sample_pos = sample_pos;

	return no_error;
}*/

/*
	Read one INTEL word (little-endian 16-bit word)

static error_t read_wave_intel_word(FILE *handle, intel_word_t *reply)
{
	unsigned char buf[2];

	if (fread(buf, 1, 2, handle) != 2)
		return read_error;

	*reply = (buf[0] & 0xff) | ((buf[1] & 0xff) << 8);

	return no_error;
}

/*
	Read one INTEL double word (little-endian 32-bit word)

static error_t read_wave_intel_dword(FILE *handle, intel_dword_t *reply)
{
	unsigned char buf[4];

	if (fread(buf, 1, 4, handle) != 4)
		return read_error;

	*reply = (buf[0] & 0xff) | ((buf[1] & 0xff) << 8) | ((buf[2] & 0xff) << 16) | ((buf[3] & 0xff) << 24);

	return no_error;
}

/*
	Read one RIFF tag (4 byte-long tag)

static error_t read_wave_tag(FILE *handle, riff_tag_t *reply)
{
	unsigned char buf[4];

	if (fread(buf, 1, 4, handle) != 4)
		return read_error;

	*reply = ((buf[0] & 0xff) << 24) | ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff);

	return no_error;
}







/* static prototypes */
static error_t write_wave_intel_word(FILE *handle, intel_word_t data);
static error_t write_wave_intel_dword(FILE *handle, intel_dword_t data);
static error_t write_wave_tag(FILE *handle, riff_tag_t data);


/*
	Parse WAVE header and fill the wave_file structure accordingly, so that
	write_wave_sample() can be called afterwards.

	handle: stdc stream pointing to start of WAVE file
	wave_file: pointer to the wave_file_t to fill

	Returns error code (no_error or incorrect_parameters, bad_wave_format,
	unsupported_wave_format)

error_t open_wave_out(FILE *handle, uint_fast32_t samplesPerSec, wave_file_out_t *wave_file)
{
	error_t error;


	/* set up handle */
	if (! handle)
		return invalid_parameters;
	wave_file->handle = handle;


	/* write RIFF header*/
	/* read 'RIFF' tag */
	if ((error = write_wave_tag(handle, tag_RIFF)) != no_error)
		return error;

	/* skip total len field */
	if ((error = write_wave_intel_dword(handle, 0)) != no_error)
		return error;
	/*wave_file->len = dword;*/

	/* write type tag ('WAVE') */
	if ((error = write_wave_tag(handle, tag_WAVE)) != no_error)
		return error;


	/* write fmt field */

	/* write field tag */
	if ((error = write_wave_tag(handle, tag_fmt)) != no_error)
		return error;
	/* write field len */
	if ((error = write_wave_intel_dword(handle, 16)) != no_error)
		return error;

	/***Fields common to all encoding***/
	/* write format tag */
	if ((error = write_wave_intel_word(handle, 1)) != no_error)	/* only PCM is supported */
		return error;
	/*wave_file->formatTag = 1;*/
	/* write number of channels */
	if ((error = write_wave_intel_word(handle, 1)) != no_error)
		return error;
	/*wave_file->channels = 1;*/
	/* write sampling rate */
	if ((error = write_wave_intel_dword(handle, samplesPerSec)) != no_error)
		return error;
	wave_file->samplesPerSec = samplesPerSec;
	/* write byte rate */
	if ((error = write_wave_intel_dword(handle, samplesPerSec/**channels*block_size*/)) != no_error)
		return error;
	/*wave_file->avgBytesPerSec = ...;*/
	/* write block alignment */
	if ((error = write_wave_intel_word(handle, 1)) != no_error)
		return error;
	/*wave_file->blockAlign = 1;*/

	/***Field specific to PCM***/
	/* write bits per sample */
	if ((error = write_wave_intel_word(handle, /*2*/8)) != no_error)
		return error;
	/*wave_file->bitsPerSample = 2;*/


	/* write data field */
	/* write field tag */
	if ((error = write_wave_tag(handle, tag_data)) != no_error)
		return error;
	/* skip field len */
	if ((error = write_wave_intel_dword(handle, 0)) != no_error)
		return error;


	wave_file->sample_pos = 0;
	wave_file->data_offset = ftell(handle);

	return no_error;
}

/*
	Close WAVE file

	We need to write the lenght of the data field and the total lenght of the file.

error_t close_wave_out(wave_file_out_t *wave_file)
{
	uint_fast32_t file_len = ftell(wave_file->handle);

	/* write len of data field */
	fseek(wave_file->handle, wave_file->data_offset-4, SEEK_SET);
	write_wave_intel_dword(wave_file->handle, (file_len-wave_file->data_offset));

	/* write len of file */
	fseek(wave_file->handle, 4, SEEK_SET);
	write_wave_intel_dword(wave_file->handle, file_len-8);

	return no_error;
}


/*
	write_wave_sample: write a sample to a wave_file_out_t

	wave_file: wave_file structure
	data: sample (should be -1 or +1)

	Returns error code (no_error or write_error)

error_t write_wave_sample(wave_file_out_t *wave_file, int data)
{
	int ch;


	ch = putc(data > 0 ? 0xC0 : 0x40, wave_file->handle);
	if (ch == EOF)
		return write_error;

	return no_error;
}

/*
	Write one INTEL word (little-endian 16-bit word)

static error_t write_wave_intel_word(FILE *handle, intel_word_t data)
{
	unsigned char buf[2];

	buf[0] = data & 0xff;
	buf[1] = (data >> 8) & 0xff;

	if (fwrite(buf, 1, 2, handle) != 2)
		return write_error;

	return no_error;
}

/*
	Write one INTEL double word (little-endian 32-bit word)

static error_t write_wave_intel_dword(FILE *handle, intel_dword_t data)
{
	unsigned char buf[4];

	buf[0] = data & 0xff;
	buf[1] = (data >> 8) & 0xff;
	buf[2] = (data >> 16) & 0xff;
	buf[3] = (data >> 24) & 0xff;

	if (fwrite(buf, 1, 4, handle) != 4)
		return write_error;

	return no_error;
}

/*
	Write one RIFF tag (4 byte-long tag)

static error_t write_wave_tag(FILE *handle, riff_tag_t data)
{
	unsigned char buf[4];

	buf[0] = (data >> 24) & 0xff;
	buf[1] = (data >> 16) & 0xff;
	buf[2] = (data >> 8) & 0xff;
	buf[3] = data & 0xff;

	if (fwrite(buf, 1, 4, handle) != 4)
		return write_error;

	return no_error;
}