/*Arculator 2.2 by Sarah Walker
  Disc drive noise*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "arc.h"
#include "config.h"
#include "disc.h"
#include "ddnoise.h"
#include "plat_sound.h"
#include "timer.h"

int ddnoise_vol=3;
int ddnoise_type=0;

typedef struct SAMPLE
{
	int16_t *data;
	int len;
	int freq;
} SAMPLE;

typedef struct chunk_t
{
	char id[4];
	uint32_t size;
} chunk_t;

typedef struct fmt_t
{
	uint16_t wFormatTag;
	uint16_t wChannels;
	uint32_t dwSamplesPerSec;
	uint32_t dwAvgBytesPerSec;
	uint16_t wBlockAlign;
	uint16_t wFmtSpecific;
} fmt_t;

static SAMPLE *load_wav(char *path, const char *fn)
{
	char path_fn[512];
	SAMPLE *s;
	chunk_t chunk;
	fmt_t fmt;
	int got_fmt = 0;
	uint8_t wave_id[4];
	FILE *f;

	append_filename(path_fn, path, fn, sizeof(path_fn));
	f = fopen(path_fn, "rb");
	if (!f)
	{
		rpclog("Can't open %s\n", path_fn);
		return NULL;
	}

	/*Read initial chunk*/
	fread(&chunk, sizeof(chunk_t), 1, f);
	if (chunk.id[0] != 'R' || chunk.id[1] != 'I' || chunk.id[2] != 'F' || chunk.id[3] != 'F')
	{
		rpclog("%s is not a RIFF file %c %c %c %c\n", path_fn,
				chunk.id[0], chunk.id[1], chunk.id[2], chunk.id[3]);
		fclose(f);
		return NULL;
	}

	fread(wave_id, sizeof(wave_id), 1, f);
	if (wave_id[0] != 'W' || wave_id[1] != 'A' || wave_id[2] != 'V' || wave_id[3] != 'E')
	{
		rpclog("%s is not a RIFF WAVE file %c %c %c %c\n", path_fn,
				wave_id[0], wave_id[1], wave_id[2], wave_id[3]);
		fclose(f);
		return NULL;
	}

	/*Search for WAVE chunk*/
	while (!feof(f))
	{
		fread(&chunk, sizeof(chunk_t), 1, f);
		if (chunk.id[0] == 'f' && chunk.id[1] == 'm' && chunk.id[2] == 't' && chunk.id[3] == ' ')
		{
//                        rpclog("Found fmt chunk\n");
			fread(&fmt, sizeof(fmt_t), 1, f);
			if (fmt.wFormatTag != 1) /*Only support PCM*/
			{
				rpclog("%s is not a PCM WAVE file\n", path_fn);
				fclose(f);
				return NULL;
			}
//                        rpclog("wChannels=%i\n", fmt.wChannels);
//                        rpclog("dwSamplesPerSec=%i\n", fmt.dwSamplesPerSec);
//                        rpclog("wFmtSpecific=%i\n", fmt.wFmtSpecific);
			got_fmt = 1;
		}
		else if (chunk.id[0] == 'd' && chunk.id[1] == 'a' && chunk.id[2] == 't' && chunk.id[3] == 'a')
		{
			rpclog("Found data chunk, len=%i\n", chunk.size);
			if (!got_fmt)
			{
				rpclog("%s has data before fmt\n", path_fn);
				fclose(f);
				return NULL;
			}
			s = malloc(sizeof(SAMPLE));
			s->freq = fmt.dwSamplesPerSec;
			if (fmt.wFmtSpecific == 16)
			{
				if (fmt.wChannels == 2)
				{
					/*Stereo, downmix to mono*/
					int c;

//                                        rpclog("16-bit, stereo\n");
					s->len = chunk.size / 4;
					s->data = malloc(s->len*2);
					for (c = 0; c < s->len; c++)
					{
						int16_t samples[2];

						fread(samples, sizeof(samples), 1, f);
						s->data[c] = (samples[0] + samples[1]) / 2;
					}
				}
				else
				{
//                                        rpclog("16-bit, mono\n");
					s->len = chunk.size / 2;
					s->data = malloc(s->len*2);
					fread(s->data, chunk.size, 1, f);
				}
			}
			else /*8 bits per sample, need to upconvert*/
			{
				if (fmt.wChannels == 2)
				{
					/*Stereo, downmix to mono*/
					int c;

//                                        rpclog("8-bit, stereo\n");
					s->len = chunk.size / 2;
					s->data = malloc(s->len*2);
					for (c = 0; c < s->len; c++)
					{
						uint8_t samples[2];

						fread(samples, sizeof(samples), 1, f);
						s->data[c] = ((int16_t)(samples[0] ^ 0x80) + (int16_t)(samples[1] ^ 0x80)) * 128;
					}
				}
				else
				{
					int c;

//                                        rpclog("8-bit, mono\n");
					s->len = chunk.size;
					s->data = malloc(s->len*2);
					for (c = 0; c < chunk.size; c++)
						s->data[c] = (getc(f) ^ 0x80) << 8;
				}
			}
			fclose(f);
			return s;
		}
		else
		{
			/*Skip over chunk*/
			fseek(f, chunk.size, SEEK_CUR);
			if (chunk.size & 1)
				fseek(f, 1, SEEK_CUR);
		}
	}

	rpclog("%s does not have WAVE chunk\n", path_fn);
	fclose(f);

	return NULL;
//        s = malloc(sizeof(SAMPLE));

}

static void destroy_sample(SAMPLE *s)
{
	if (s)
	{
		if (s->data)
			free(s->data);
		free(s);
	}
}

static SAMPLE *seeksmp[4][2];
static SAMPLE *motorsmp[3];

static float ddnoise_mpos = 0;
static int ddnoise_mstat = -1;
static int oldmotoron = 0;

static float ddnoise_spos = 0;
static int ddnoise_sstat = -1;
static int ddnoise_sdir = 0;

void ddnoise_init()
{
	char path[512];

	append_filename(path, exname, "ddnoise/35/", sizeof(path));
	rpclog("ddnoise path %s\n", path);

	seeksmp[0][0] = load_wav(path, "stepo.wav");
	if (seeksmp[0][0])
	{
		seeksmp[0][1] = load_wav(path, "stepi.wav");
		seeksmp[1][0] = load_wav(path, "seek1o.wav");
		seeksmp[1][1] = load_wav(path, "seek1i.wav");
		seeksmp[2][0] = load_wav(path, "seek2o.wav");
		seeksmp[2][1] = load_wav(path, "seek2i.wav");
		seeksmp[3][0] = load_wav(path, "seek3o.wav");
		seeksmp[3][1] = load_wav(path, "seek3i.wav");
	}
	else
	{
		seeksmp[0][0] = load_wav(path, "step.wav");
		seeksmp[0][1] = load_wav(path, "step.wav");
		seeksmp[1][0] = load_wav(path, "seek.wav");
		seeksmp[1][1] = load_wav(path, "seek.wav");
		seeksmp[2][0] = load_wav(path, "seek3.wav");
		seeksmp[2][1] = load_wav(path, "seek3.wav");
		seeksmp[3][0] = load_wav(path, "seek2.wav");
		seeksmp[3][1] = load_wav(path, "seek2.wav");
	}
	motorsmp[0] = load_wav(path, "motoron.wav");
	motorsmp[1] = load_wav(path, "motor.wav");
	motorsmp[2] = load_wav(path, "motoroff.wav");
}

void ddnoise_close()
{
	int c;
	for (c = 0; c < 4; c++)
	{
		if (seeksmp[c][0]) destroy_sample(seeksmp[c][0]);
		if (seeksmp[c][1]) destroy_sample(seeksmp[c][1]);
		seeksmp[c][0] = seeksmp[c][1] = NULL;
	}
	for (c = 0; c < 3; c++)
	{
		if (motorsmp[c]) destroy_sample(motorsmp[c]);
		motorsmp[c] = NULL;
	}
}

static int16_t ddbuffer[4410];

void ddnoise_seek(int len)
{
//        timer_set_delay_u64(&fdc_timer, 2500 * TIMER_USEC);
//        rpclog("Seek %i tracks\n",len);
	ddnoise_sdir = (len < 0) ? 1 : 0;
	if (len < 0) len = -len;
	ddnoise_spos = 0;
	if (len == 0)
	{
		ddnoise_sstat = -1;
		timer_set_delay_u64(fdc_timer, 200 * TIMER_USEC);
//                rpclog("fdc_time set by ddnoise_seek 0\n");
	}
	else if (len == 1)
	{
		ddnoise_sstat = 0;
		timer_set_delay_u64(fdc_timer, 6000 * TIMER_USEC);
//                rpclog("fdc_time set by ddnoise_seek 1\n");
	}
	else if (len < 7)
		ddnoise_sstat = 1;
	else if (len < 30)
		ddnoise_sstat = 2;
	else
		ddnoise_sstat = 3;
//        if (!sound_ddnoise) fdc_time = 200;
//        rpclog("Start seek!\n");
}

void ddnoise_mix()
{
	int c;
//        if (!f1) f1=fopen("f1.pcm","wb");
//        if (!f2) f2=fopen("f2.pcm","wb");

	memset(ddbuffer, 0, 4410 * 2);
//        fwrite(ddbuffer,4410*2,1,f1);
	if (motoron && !oldmotoron)
	{
		ddnoise_mstat = 0;
		ddnoise_mpos = 0;
	}
	if (!motoron && oldmotoron)
	{
		ddnoise_mstat = 2;
		ddnoise_mpos = 0;
	}

	if (1)//sound_ddnoise)
	{
		for (c = 0; c < 4410; c++)
		{
			ddbuffer[c] = 0;
			if (ddnoise_mstat >= 0)
			{
				if (ddnoise_mpos >= motorsmp[ddnoise_mstat]->len)
				{
					ddnoise_mpos = 0;
					if (ddnoise_mstat != 1) ddnoise_mstat++;
					if (ddnoise_mstat == 3) ddnoise_mstat = -1;
				}
				if (ddnoise_mstat != -1)
				{
					ddbuffer[c] += ((int16_t)(((int16_t *)motorsmp[ddnoise_mstat]->data)[(int)ddnoise_mpos]) / 2);
					ddnoise_mpos += ((float)motorsmp[ddnoise_mstat]->freq / 44100.0);
				}
			}
		}

		for (c = 0; c < 4410; c++)
		{
			if (ddnoise_sstat >= 0)
			{
				if (ddnoise_spos >= seeksmp[ddnoise_sstat][ddnoise_sdir]->len)
				{
					if (ddnoise_sstat > 0)
					{
//                                                rpclog("fdc_time set by ddnoise_mix\n");
						timer_set_delay_u64(fdc_timer, 200 * TIMER_USEC);
					}
					ddnoise_spos = 0;
					ddnoise_sstat = -1;
				}
				else
				{
					ddbuffer[c] += ((int16_t)(((int16_t *)seeksmp[ddnoise_sstat][ddnoise_sdir]->data)[(int)ddnoise_spos]) / 2);
					ddnoise_spos += ((float)seeksmp[ddnoise_sstat][ddnoise_sdir]->freq / 44100.0);
				}
			}
			ddbuffer[c] = (ddbuffer[c] / 3) * ddnoise_vol;
		}
	}

	sound_givebufferdd(ddbuffer);

	oldmotoron=motoron;
}
