#include "retrohack.h"
#include <memory.h>
#include <stdio.h>

extern void __ay_Update(Uint32 cycles);
extern void __ay_Init(void);
extern void __ay_Exit(void);
extern void __ay_Pause(void);
extern void __ay_Unpause(void);

// some #defines to set out timing and memory size parameters
#define __RT_CYCLESPERFRAME (__RT_CYCLESPERSECOND / 50)
#define __RT_CYCLESPERLINE	(__RT_CYCLESPERFRAME / 311)
#define __RT_GRAPHICSMEMORY 65536

int __rt_fullscreen = FALSE;		// stores whether the application is currently fullscreen
int __rt_expendedcycles = 0;		// counts the number of cycles expended this frame
int __rt_bufptr = 0;				// determines which back buffer is currently being drawn to
int __rt_freememory;				// records the remaining free sprite memory
unsigned int __rt_TotalCycles = 0;	// counts the number of cycles expended since the program began

BITMAP *__rt_backbuf[2];			// two bitmaps for the two framebuffers

/* callbacks for Allegro */
volatile int __rt_timervar;	// timervar is used for timing
void __rt_timerfunc(void)	// timerfunc - just increments timervar
{
	__rt_timervar++;
}
END_OF_FUNCTION(__rt_timerfunc);

volatile int __rt_quitvar = FALSE;	// quitvar is set to TRUE by quitfunc if an OS quit is requested
void __rt_quitfunc(void)
{
	__rt_quitvar = TRUE;
}
END_OF_FUNCTION(__rt_quitvar);

/*
	SetPalette - sets the YCbCr palette. A close lift from the old Back2Hack code.

	COLOUR_MASK is exclusive ORd with the palette positioning. It's used to ensure that colour 0 is
	black, for any OSs that use colour 0 as the border colour (which might just be decrepit DOS,
	but might be Windows too?)
*/
#define COLOUR_MASK	10
void __rt_SetPalette(void)
{
	/*

	works in a 'y, cb, cr' colour space with four bits for y and 2 for each of
	cb and cr

	*/

	float y, cb, cr;
	float r, g, b;
	int c;
	PALETTE pal;

	c = 256;
	while(c--)
	{
		/* get rec. 601 components */

		y = (((float)(c&0xf0)*219.0f)/256.0f) + 16.0f;
		cb = (float)(((c << 4)&0xc0)-128)*224.0f/256.0f + 128.0f;
		cr = (float)(((c << 6)&0xc0)-128)*224.0f/256.0f + 128.0f;

		/* do initial conversion subtraction */
		y -= 16;
		cb -= 128;
		cr -= 128;

		/* get r, g, b */
		r = 0.00456621f * y + 0.00625893f * cr;
		g = 0.00456621f * y - 0.00153632f * cb - 0.00318811f * cr;
		b = 0.00456621f * y + 0.00791071f * cb;

		if(r > 1) r = 1; if(r < 0) r = 0;
		if(g > 1) g = 1; if(g < 0) g = 0;
		if(b > 1) b = 1; if(b < 0) b = 0;

		/* run through matrix */
		pal[c^COLOUR_MASK].r = (int)(r*63.0f);
		pal[c^COLOUR_MASK].g = (int)(g*63.0f);
		pal[c^COLOUR_MASK].b = (int)(b*63.0f);
	}

	set_palette(pal);
}

/*
	ToggleFullScreen - toggles into and out of full screen, and sets the palette. This may recurse if
	necessary. Tries 320x240 if fullscreen, 640x480 if that fails. Only tries 640x80 if windowed.
*/
int __rt_ToggleFullScreen(int level)
{
	if(level >= 2) return FALSE;

	if(__rt_fullscreen ^= TRUE)
	{
		if(set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 320, 240, 0, 0))
			if(set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0))
				return __rt_ToggleFullScreen(level+1);
	}
	else
	{
		if(set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0))
			return __rt_ToggleFullScreen(level+1);
	}
	__rt_SetPalette();
	clear_to_color(screen, COLOUR_MASK);
	set_close_button_callback(__rt_quitfunc);
	select_mouse_cursor( __rt_fullscreen ? MOUSE_CURSOR_NONE : MOUSE_CURSOR_ARROW);
	return TRUE;
}

/*
	Some #defines to define the behaviour of the sleep timer
*/
#define TIMER_BPS			250
#define TICKS_PER_FRAME		(TIMER_BPS/50)
#define	TIMER_MULTIPLIER	(1000/TIMER_BPS)

/*
	The Init func. Inits Allegro and the AY, locks various things, creates the framebuffer BITMAPs,
	initialises the free memory count
*/
int rt_Init(void)
{
	allegro_init();
	install_keyboard();
	install_timer();
	enable_hardware_cursor();
	__ay_Init();

	LOCK_FUNCTION(__rt_timerfunc);
	LOCK_VARIABLE(__rt_timervar);
	LOCK_FUNCTION(__rt_quitfunc);
	LOCK_VARIABLE(__rt_quitvar);

	install_int_ex(__rt_timerfunc, BPS_TO_TIMER(TIMER_BPS));

	__rt_freememory = __RT_GRAPHICSMEMORY;
	__rt_backbuf[0] = create_bitmap(320, 240); clear_to_color(__rt_backbuf[0], COLOUR_MASK);
	__rt_backbuf[1] = create_bitmap(320, 240); clear_to_color(__rt_backbuf[1], COLOUR_MASK);
	__rt_bufptr = 0;

	__rt_fullscreen ^= TRUE;
	return __rt_ToggleFullScreen(0);
}

/*
	Exit - frees some memory, not much more
*/
void rt_Exit(void)
{
	int c = 2;
	__ay_Exit();
	while(c--)
	{
		if(__rt_backbuf[c])
		{
			destroy_bitmap(__rt_backbuf[c]);
			__rt_backbuf[c] = NULL;
		}
	}
}

/*
	WaitCycles - waits for the given number of cycles, toggles full screen if it is being requested,
	calls the AY callback
*/
unsigned int __rt_AYOffset = 0;
void rt_WaitCycles(int cycles)
{
	__rt_TotalCycles += cycles;
	__rt_expendedcycles += cycles;
	__ay_Update((cycles + __rt_AYOffset) / __RT_AYDIVIDER); __rt_AYOffset = (__rt_AYOffset + cycles) % __RT_AYDIVIDER;
	if(__rt_expendedcycles >= __RT_CYCLESPERFRAME)
	{
		if(key[KEY_ALT] && key[KEY_ENTER])
		{
			__ay_Pause();
			__rt_ToggleFullScreen(0);
			__ay_Unpause();
		}

		do
		{
			__rt_expendedcycles -= __RT_CYCLESPERFRAME;
			if(__rt_timervar > TICKS_PER_FRAME)
			{
				__rt_timervar = 0;
			}
			else
			{
				rest( (TICKS_PER_FRAME - __rt_timervar) * TIMER_MULTIPLIER);
				__rt_timervar -= TICKS_PER_FRAME;
			}
		}
		while(__rt_expendedcycles >= __RT_CYCLESPERFRAME);
	}
}

/*
	WaitEvent - works out how many cycles until the next event of this sort, waits that many
*/
void rt_WaitEvent(int event)
{
	switch(event)
	{
		default: return;
		case RT_HSYNC:
			rt_WaitCycles(__RT_CYCLESPERLINE - (__rt_expendedcycles%__RT_CYCLESPERLINE));
		break;
		case RT_VSYNC:
			rt_WaitCycles(__RT_CYCLESPERFRAME - (__rt_expendedcycles%__RT_CYCLESPERFRAME));
		break;
	}
}

/*
	Flip - flips the framebuffers, waits for hsync
*/
void rt_Flip()
{
	rt_WaitEvent(RT_VSYNC);
	stretch_blit(__rt_backbuf[__rt_bufptr], screen, 0, 0, 320, 240, 0, 0, SCREEN_W, SCREEN_H);
	__rt_bufptr ^= 1;
}

/*
	The graphic struct stores information about uploaded graphics. 16 copies are made (one for
	each possible colour passed to the Blit function) and the maskcolours are computed
*/
struct Graphic
{
	BITMAP *images[16];
	int MaskCols[16];
	int memsize;
};

/*
	FreeGraphic releases all the memory used by a graphic
*/
void rt_FreeGraphic(void *graphic)
{
	struct Graphic *g = (struct Graphic *)graphic;
	int c = 16;

	if(!g) return;

	while(c--)
		destroy_bitmap(g->images[c]);
	__rt_freememory += g->memsize;
	free(graphic);
	rt_WaitCycles(1);
}

/*
	These two define timing for the UploadGraphic function
*/
#define __RT_UPLOADSTATICCOST		60
#define __RT_COSTPERPIXEL			8

void *rt_UploadGraphic(enum rt_TargetFormats format, unsigned char *data, int pixelsperbyte, int width, int height, int maskcol)
{
	struct Graphic *newgraphic;
	int x, y, c;

	// work out how much memory this graphic occupies.
	int memorysize = ((width+1) >> 1)*height;

	// don't allow an upload if there is no memory free
	if( memorysize > __rt_freememory)
		return NULL;

	// don't allow sprites of more than 512 pixels in either direction
	if(width > 512 || height > 512) return NULL;

	// in this, the Allegro implementation, graphics are just memory bitmaps
	newgraphic = (struct Graphic *)malloc(sizeof(struct Graphic));
	newgraphic->memsize = memorysize;

	// work out the 16 sprite variations and store them all for later
	c = 16;
	while(c--)
	{
		// copy data into BITMAP
		newgraphic->images[c] = create_bitmap(width, height);
		for(y = 0; y < height; y++)
		{
			for(x = 0; x < width; x++)
			{
				Uint8 Colour;
				switch(pixelsperbyte)
				{
					default:
					case 1:
						Colour = data[(y * width) + x]&0xf;
					break;
					case 2:
					{
						int Addr = (y * ((width +1) >> 1)) + (x >> 1);
						Colour = (x&1) ? data[Addr]&0xf : data[Addr] >> 4;
					}
					break;
				}

				switch(format)
				{
					default:
					case RT_CbCr:
						putpixel(newgraphic->images[c], x, y, (Colour | (c << 4)) ^COLOUR_MASK);
					break;
					case RT_Y:
						putpixel(newgraphic->images[c], x, y, ((Colour << 4) | c) ^COLOUR_MASK);
					break;
					case RT_MIX:
						putpixel(newgraphic->images[c], x, y, (((Colour & 0xb) << 4) | ((c&0xb) << 2) | ((Colour & 0x2) << 2) | ((c&0x2) << 1) | ((Colour & 0x1) << 1) | (c&0x1))^COLOUR_MASK);
					break;
				}
			}
		}

		// work out what the masked colour maps to in the main palette
		switch(format)
		{
			default:
			case RT_CbCr:
				newgraphic->MaskCols[c] = ((maskcol & 0xf) | (c << 4)) ^COLOUR_MASK;
			break;
			case RT_Y:
				newgraphic->MaskCols[c] = (((maskcol & 0xf) << 4) | c) ^COLOUR_MASK;
			break;
			case RT_MIX:
				newgraphic->MaskCols[c] = (((maskcol & 0xb) << 4) | ((c&0xb) << 2) | ((maskcol&0x2) << 2) | ((c&0x2) << 1) | ((maskcol&0x1) << 1) | (c&0x1))^COLOUR_MASK;
			break;
		}
	}

	// reduce the amoutn of free memory and apply the processing cost
	__rt_freememory -= memorysize;
	rt_WaitCycles(__RT_UPLOADSTATICCOST + width*height*__RT_COSTPERPIXEL);
	return (void *)newgraphic;
}

/*
	Two functions for drawing pre-clipped and boundary aligned scanlines from sprites to the screen
*/
void __rt_BlitInline(BITMAP *src, int srcx, int srcxadd, int destx, unsigned char *Dest, unsigned char *Src, int maskcol)
{
	while(srcx < (src->w << 22) && srcx >= 0 && ((destx >> 16) < 320))
	{
		Dest[destx >> 16] = Src[srcx >> 22];
		srcx += srcxadd;
		destx += 65536;
	}
}

void __rt_MaskedBlitInline(BITMAP *src, int srcx, int srcxadd, int destx, unsigned char *Dest, unsigned char *Src, int maskcol)
{
	while(srcx < (src->w << 22) && srcx >= 0 && ((destx >> 16) < 320))
	{
		if(Src[srcx >> 22] != maskcol) Dest[destx >> 16] = Src[srcx >> 22];
		srcx += srcxadd;
		destx += 65536;
	}
}

/*
	BlitSetup, called per scanline drawn, works out the scaling adder, clips to the screen and aligns
	to the next whole pixel
*/
void __rt_BlitSetup(BITMAP *src, int srcy, int desty, int destx, int scalex, int maskcol, void (* __rt_BlitF)(BITMAP *src, int srcx, int srcxadd, int destx, unsigned char *Dest, unsigned char *Src, int maskcol))
{
	int srcxadd, srcx;
	unsigned char *Src = (unsigned char *)src->line[srcy], *Dest;

	if(desty < 0) return;
	if(desty >= 240) return;
	if(!scalex) return;
	Dest = __rt_backbuf[__rt_bufptr]->line[desty];

	srcxadd = fixdiv(4194304, scalex);

	if(srcxadd >= 0)
	{
		srcx = 0;
	}
	else
	{
		srcx = (src->w << 22)-1;
		destx += src->w*scalex;
	}

	// left clip
	if(destx < 0)
	{
		int add = -destx;
		srcx += fmul(add, srcxadd);
		destx = 0;
	}

	// align to next pixel boundary
	if(destx&65535)
	{
		int add = 65536 - (destx&65535);
		srcx += fmul(add, srcxadd);
		destx += add;
	}

	__rt_BlitF(src, srcx, srcxadd, destx, Dest, Src, maskcol);
}

/*
	The actual Blit function. Is as per the documented functions, but takes a final argument that
	affects which scanline blitter is used and therefore whether masking is enabled
*/
#define __RT_BLITSTATICCOST 25
#define __RT_BLITVCOST		15
void __rt_Blit(void *graphic, int highnibble, int x, int y, int scalex, int scaley, int skew, int scalechange, void (* __rt_BlitF)(BITMAP *src, int srcx, int srcxadd, int destx, unsigned char *Dest, unsigned char *Src, int maskcol))
{
	struct Graphic *g = (struct Graphic *)graphic;
	int srcy, srcyadd;

	if(!g) return;

	// 0 scaley will make the loops below never end
	if(!scaley) return;

	highnibble &= 0xf;
	rt_WaitCycles(__RT_BLITSTATICCOST);
	srcyadd = fixdiv(4194304, scaley);

	// pixels located in centre of square. Adjust!
	x -= 32768;
	y -= 32768;

	acquire_bitmap(__rt_backbuf[__rt_bufptr]);
	acquire_bitmap(g->images[highnibble]);

	// from here down, the pixel is located in the top left of the square
	if(scaley > 0)
	{
		srcy = 0;	// start at the top, draw downward
	}
	else
	{
		srcy = (g->images[0]->h << 22) - 1; // start at the bottom, draw upward

		y += scaley*g->images[0]->h; // move up the required number of pixels
		x -= skew*g->images[0]->h;
		scalex -= scalechange*g->images[0]->h;
	}

	// move to next horizontal boundary
	if(y&65535)
	{
		int mul = 65536 - (y&65535);

		srcy += fmul(mul, srcyadd);
		scalex += fmul(mul, scalechange);
		x += fmul(mul, skew);

		y += 65536 - (y&65535);
	}

	while(srcy >= 0 && srcy < g->images[0]->h << 22)
	{
		int x1 = (x + 65535) >> 16;
		int x2 = (x + scalex*g->images[0]->w + 65535) >> 16;
		rt_WaitCycles(__RT_BLITVCOST + abs(x2 - x1));

		__rt_BlitSetup(g->images[highnibble], srcy >> 22, y >> 16, x, scalex, g->MaskCols[highnibble], __rt_BlitF);

		y += 65536;
		x += skew;
		scalex += scalechange;
		srcy += srcyadd;
	}

	release_bitmap(__rt_backbuf[__rt_bufptr]);
	release_bitmap(g->images[highnibble]);
}

/*
	the two documented blitting functions
*/
void rt_Blit(void *graphic, int highnibble, int x, int y, int scalex, int scaley, int skew, int scalechange)
{
	__rt_Blit(graphic, highnibble, x, y, scalex, scaley, skew, scalechange, __rt_BlitInline);
}

void rt_MaskedBlit(void *graphic, int highnibble, int x, int y, int scalex, int scaley, int skew, int scalechange)
{
	__rt_Blit(graphic, highnibble, x, y, scalex, scaley, skew, scalechange, __rt_MaskedBlitInline);
}

/*
	the two simulator functions (note: both are 'free')
*/
int rtSim_QuitWanted(void)
{
	return __rt_quitvar;
}

void rtSim_SetWindowTitle(const char *n)
{
	set_window_title(n);
}

/*
	ReadKeyboard. Doesn't do much more than reformat Allegro's key array into something a bit less
	convenient
*/
Uint8 rt_ReadKeyboard(int addr)
{
	Uint8 r = 0;
	rt_WaitCycles(1);

	switch(addr)
	{
		case 0:
			r = (
					(key[KEY_6] ? 0x80 : 0x00) |
					(key[KEY_5] ? 0x40 : 0x00) |
					(key[KEY_4] ? 0x20 : 0x00) |
					(key[KEY_3] ? 0x10 : 0x00) |
					(key[KEY_2] ? 0x08 : 0x00) |
					(key[KEY_1] ? 0x04 : 0x00) |
					(key[KEY_ESC] ? 0x02 : 0x00)
				);
		break;

		case 1:
			r = (
					(key[KEY_7] ? 0x80 : 0x00) |
					(key[KEY_8] ? 0x40 : 0x00) |
					(key[KEY_9] ? 0x20 : 0x00) |
					(key[KEY_0] ? 0x10 : 0x00) |
					(key[KEY_MINUS] ? 0x08 : 0x00) |
					(key[KEY_EQUALS] ? 0x04 : 0x00) |
					(key[KEY_BACKSPACE] ? 0x02 : 0x00)
				);
		break;

		case 2:
			r = (
					(key[KEY_Y] ? 0x80 : 0x00) |
					(key[KEY_T] ? 0x40 : 0x00) |
					(key[KEY_R] ? 0x20 : 0x00) |
					(key[KEY_E] ? 0x10 : 0x00) |
					(key[KEY_W] ? 0x08 : 0x00) |
					(key[KEY_Q] ? 0x04 : 0x00) |
					(key[KEY_TAB] ? 0x02 : 0x00)
				);
		break;

		case 3:
			r = (
					(key[KEY_U] ? 0x80 : 0x00) |
					(key[KEY_I] ? 0x40 : 0x00) |
					(key[KEY_O] ? 0x20 : 0x00) |
					(key[KEY_P] ? 0x10 : 0x00) |
					(key[KEY_OPENBRACE] ? 0x08 : 0x00) |
					(key[KEY_CLOSEBRACE] ? 0x04 : 0x00) |
					(key[KEY_ENTER] ? 0x02 : 0x00)
				);
		break;

		case 4:
			r = (
					(key[KEY_H] ? 0x80 : 0x00) |
					(key[KEY_G] ? 0x40 : 0x00) |
					(key[KEY_F] ? 0x20 : 0x00) |
					(key[KEY_D] ? 0x10 : 0x00) |
					(key[KEY_S] ? 0x08 : 0x00) |
					(key[KEY_A] ? 0x04 : 0x00)
				);
		break;

		case 5:
			r = (
					(key[KEY_J] ? 0x80 : 0x00) |
					(key[KEY_K] ? 0x40 : 0x00) |
					(key[KEY_L] ? 0x20 : 0x00) |
					(key[KEY_COLON] ? 0x10 : 0x00) |
					(key[KEY_QUOTE] ? 0x08 : 0x00) |
					(key[KEY_TILDE] ? 0x04 : 0x00)
				);
		break;

		case 6:
			r = (
					(key[KEY_B] ? 0x80 : 0x00) |
					(key[KEY_V] ? 0x40 : 0x00) |
					(key[KEY_C] ? 0x20 : 0x00) |
					(key[KEY_X] ? 0x10 : 0x00) |
					(key[KEY_Z] ? 0x08 : 0x00) |
					(key[KEY_BACKSLASH] ? 0x04 : 0x00) |
					(key[KEY_LSHIFT] ? 0x02 : 0x00)
				);
		break;

		case 7:
			r = (
					(key[KEY_N] ? 0x80 : 0x00) |
					(key[KEY_M] ? 0x40 : 0x00) |
					(key[KEY_COMMA] ? 0x20 : 0x00) |
					(key[KEY_STOP] ? 0x10 : 0x00) |
					(key[KEY_SLASH] ? 0x08 : 0x00) |
					(key[KEY_RSHIFT] ? 0x04 : 0x00)
				);
		break;

		case 8:
			r = (
					(key[KEY_SPACE] ? 0x80 : 0x00) |
					(key[KEY_ALT] ? 0x40 : 0x00) |
					(key[KEY_LCONTROL] ? 0x20 : 0x00)
				);
		break;

		case 9:
			r = (
					(key[KEY_ALTGR] ? 0x80 : 0x00) |
					(key[KEY_RCONTROL] ? 0x40 : 0x00)
				);
		break;

		case 10:
			r = (
					(key[KEY_RIGHT] ? 0x80 : 0x00) |
					(key[KEY_UP] ? 0x40 : 0x00) |
					(key[KEY_DOWN] ? 0x20 : 0x00) |
					(key[KEY_LEFT] ? 0x10 : 0x00)
				);
		break;
		default: break;
	}

	return r^0xff;
}

/*

	=============================
	AY EMULATION CODE STARTS HERE
	=============================

	This is liberally lifted from my Spectrum emulator, and then uglified into ordinary C

*/

/* volume levels, 'liberated' from FUSE */
const int __ay_Levels[16]=
{
	0x0000 >> 2, 0x0385 >> 2, 0x053D >> 2, 0x0770 >> 2,
	0x0AD7 >> 2, 0x0FD5 >> 2, 0x15B0 >> 2, 0x230C >> 2,
	0x2B4C >> 2, 0x43C1 >> 2, 0x5A4B >> 2, 0x732F >> 2,
	0x9204 >> 2, 0xAFF1 >> 2, 0xD921 >> 2, 0xFFFF >> 2
};

#define CULA_AUDIOEVENT_LENGTH	65536

Uint8 __ay_Registers[16], __ay_CReg;

struct Channel
{
	Uint32 Pitch, ToneMask, NoiseMask;
	int Volume, *VolPtr;
	unsigned int SamplePos, SampleAdd;
} __ay_Channels[3];
Uint32 __ay_NoisePos, __ay_NoiseAdd;

struct AudioEvent
{
	Uint8 Value, Register;
	Uint32 SampleDiff, ClockTime, Remainder;
};

Sint32 __ay_EnvelopePos, __ay_EnvelopeAdd, __ay_EnvelopePitch, __ay_EnvelopeMask;
int __ay_EnvelopeVolume;

int *__ay_EnvelopePattern;
int __ay_EnvelopePatterns[5][32];
Uint16 __ay_NoiseWave[1024];

struct AudioEvent __ay_AudioBuffer[CULA_AUDIOEVENT_LENGTH];
Uint32 __ay_AudioWritePtr, __ay_AudioReadPtr;

Uint32 __ay_AudioMask, __ay_AudioProcessTime, __ay_TotalTime, __ay_AudioNumerator;
int __ay_AudioEnabled;
AUDIOSTREAM *__ay_audiostream;

Sint16 __ay_SpeakerLevel;

#define TONE_BIT	0x400000
#define TONE_SHIFT	22

// AY clock rate
#define CLOCK_RATE 3545400
#define SAMPLE_RATE	44100
#define BUFFER_SIZE	1

unsigned int __ay_CurrentBufferSize, __ay_WrittenSamples, __ay_SampleComparitor;

void __ay_WriteAudioEvent(Uint32 TimeStamp)
{
	Uint32 Difference;
	__ay_AudioBuffer[__ay_AudioWritePtr].ClockTime = TimeStamp;

	Difference = TimeStamp - __ay_AudioBuffer[(__ay_AudioWritePtr-1)&(CULA_AUDIOEVENT_LENGTH-1)].ClockTime;

	__ay_AudioBuffer[__ay_AudioWritePtr].SampleDiff = (Difference*SAMPLE_RATE + __ay_AudioBuffer[(__ay_AudioWritePtr-1)&(CULA_AUDIOEVENT_LENGTH-1)].Remainder) / CLOCK_RATE;
	__ay_AudioBuffer[__ay_AudioWritePtr].Remainder = (Difference*SAMPLE_RATE + __ay_AudioBuffer[(__ay_AudioWritePtr-1)&(CULA_AUDIOEVENT_LENGTH-1)].Remainder) % CLOCK_RATE;
	__ay_AudioWritePtr = (__ay_AudioWritePtr+1)&(CULA_AUDIOEVENT_LENGTH-1);
}

void __ay_Init(void)
{
	int c = 1024;
	__ay_CReg = 0;
	__ay_TotalTime = 0;

	/* setup noise and patterns */
	while(c--)
	{
		__ay_NoiseWave[c] = rand() / (RAND_MAX >> 1);
	}

	__ay_EnvelopePatterns[1][0] = 0;
	__ay_EnvelopePatterns[3][0] =
	__ay_EnvelopePatterns[4][0] = 15;

	c = 16;
	while(c--)
	{
		__ay_EnvelopePatterns[0][c] = c;
		__ay_EnvelopePatterns[1][c+1] = c^0xf;

		__ay_EnvelopePatterns[2][c] = c;
		__ay_EnvelopePatterns[2][c+16] = c^0xf;

		__ay_EnvelopePatterns[3][c+1] = c;
		__ay_EnvelopePatterns[4][c] = c^0xf;
	}

	c = 16;
	while(c--)
	{
		__ay_EnvelopePatterns[0][c+16] = __ay_EnvelopePatterns[0][c];
		__ay_EnvelopePatterns[1][c+16] = __ay_EnvelopePatterns[1][c];
		__ay_EnvelopePatterns[3][c+16] = __ay_EnvelopePatterns[3][c];
		__ay_EnvelopePatterns[4][c+16] = __ay_EnvelopePatterns[4][c];
	}

	__ay_EnvelopePattern = __ay_EnvelopePatterns[0];
	__ay_EnvelopeAdd = 0;
	__ay_EnvelopePos = 0;
	__ay_WrittenSamples = __ay_SampleComparitor =  0;

	c = 5;
	while(c--)
	{
		int ic = 32;
		while(ic--)
			__ay_EnvelopePatterns[c][ic] = __ay_Levels[__ay_EnvelopePatterns[c][ic]];
	}

	/* initialise audio */
	reserve_voices(1, 0);
	install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL);

	__ay_AudioWritePtr = __ay_AudioReadPtr = 0;
	__ay_AudioProcessTime = __ay_AudioMask = 0;
	__ay_AudioBuffer[CULA_AUDIOEVENT_LENGTH-1].ClockTime = 0;

	if(__ay_audiostream = play_audio_stream(__ay_CurrentBufferSize = BUFFER_SIZE, 16, FALSE, SAMPLE_RATE, 128, 128))
	{
		int c = 3;
		__ay_AudioEnabled = TRUE;
		__ay_AudioNumerator = (Uint32)(((float)CLOCK_RATE / (float)(SAMPLE_RATE << 4)) * (1 << TONE_SHIFT));

		while(c--)
		{
			__ay_Channels[c].Pitch = __ay_Channels[c].ToneMask = __ay_Channels[c].NoiseMask = 0;
			__ay_Channels[c].Volume = 0;
			__ay_Channels[c].VolPtr = &__ay_Channels[c].Volume;
			__ay_Channels[c].SamplePos = __ay_Channels[c].SampleAdd = 0;
		}
	}
	else
	{
		__ay_AudioEnabled = FALSE;
	}
}

void __ay_Exit(void)
{
	if(__ay_audiostream)
	{
		stop_audio_stream(__ay_audiostream);
		__ay_audiostream = NULL;
	}
	remove_sound();
}

void __ay_Pause(void)
{
	stop_audio_stream(__ay_audiostream); __ay_audiostream = NULL;
}

void __ay_Unpause(void)
{
	__ay_audiostream = play_audio_stream(__ay_CurrentBufferSize, 16, FALSE, SAMPLE_RATE, 128, 128);
}

void __ay_DoubleBufferSize(void)
{
	__ay_Pause();
	__ay_CurrentBufferSize <<= 1;
	__ay_Unpause();
}

void __ay_HalveBufferSize(void)
{
	__ay_Pause();
	__ay_CurrentBufferSize >>= 1;
	__ay_Unpause();
}

Uint8 ay_Read(void)
{
	if(__rt_TotalCycles%__RT_AYDIVIDER)
		rt_WaitCycles(__RT_AYDIVIDER - (__rt_TotalCycles%__RT_AYDIVIDER));
	rt_WaitCycles(__RT_AYDIVIDER);
	return __ay_Registers[__ay_CReg];
}

void ay_Write(Uint16 Addr, Uint8 Value8)
{
	switch(Addr)
	{
		case 0xfffd:
			__ay_CReg = Value8&15;
		break;

		case 0xbffd:
			switch(__ay_CReg)
			{
				case 1: case 3: case 5: 
				case 13: Value8 &= 0x0f; break;

				case 8: case 9: case 10:
				case 6: Value8 &= 0x1f; break;
			}
			__ay_Registers[__ay_CReg] = Value8;

			__ay_AudioBuffer[__ay_AudioWritePtr].Register = __ay_CReg;
			__ay_AudioBuffer[__ay_AudioWritePtr].Value = Value8;
			__ay_WriteAudioEvent(__rt_TotalCycles / __RT_AYDIVIDER);
		break;

		case AY_TOGGLEADDR:
			__ay_AudioBuffer[__ay_AudioWritePtr].Register = 16;
			__ay_AudioBuffer[__ay_AudioWritePtr].Value = Value8;
			__ay_WriteAudioEvent(__rt_TotalCycles / __RT_AYDIVIDER);
		break;
	}
	if(__rt_TotalCycles%__RT_AYDIVIDER)
		rt_WaitCycles(__RT_AYDIVIDER - (__rt_TotalCycles%__RT_AYDIVIDER));
	rt_WaitCycles(__RT_AYDIVIDER);
}

void __ay_FixChannel(int c)
{
	__ay_Channels[c].SampleAdd = __ay_AudioNumerator / (__ay_Channels[c].Pitch+1);

	if(__ay_Channels[c].SampleAdd > TONE_BIT)
	{
		__ay_Channels[c].SampleAdd = 0;
		__ay_Channels[c].SamplePos = TONE_BIT;
	}
}

void __ay_FixEnvelope(void)
{
	__ay_EnvelopeAdd = (__ay_AudioNumerator >> 4) / (__ay_EnvelopePitch+1);
}

void __ay_EnactAudioEvent(void)
{
	// enact top event
	switch(__ay_AudioBuffer[ __ay_AudioReadPtr ].Register)
	{
		case 0:	__ay_Channels[0].Pitch = (__ay_Channels[0].Pitch&0xf00) | __ay_AudioBuffer[ __ay_AudioReadPtr ].Value; __ay_FixChannel(0); break;
		case 1: __ay_Channels[0].Pitch = (__ay_Channels[0].Pitch&0x0ff) | (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value << 8); __ay_FixChannel(0); break;

		case 2:	__ay_Channels[1].Pitch = (__ay_Channels[1].Pitch&0xf00) | __ay_AudioBuffer[ __ay_AudioReadPtr ].Value; __ay_FixChannel(1); break;
		case 3: __ay_Channels[1].Pitch = (__ay_Channels[1].Pitch&0x0ff) | (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value << 8); __ay_FixChannel(1); break;

		case 4:	__ay_Channels[2].Pitch = (__ay_Channels[2].Pitch&0xf00) | __ay_AudioBuffer[ __ay_AudioReadPtr ].Value; __ay_FixChannel(2); break;
		case 5: __ay_Channels[2].Pitch = (__ay_Channels[2].Pitch&0x0ff) | (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value << 8); __ay_FixChannel(2); break;

		case 6:	__ay_NoiseAdd = __ay_AudioNumerator / (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value+1);	break;

		case 7:
			__ay_Channels[0].ToneMask = (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value&0x01) ? 0x0000 : 0xffff;
			__ay_Channels[1].ToneMask = (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value&0x02) ? 0x0000 : 0xffff;
			__ay_Channels[2].ToneMask = (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value&0x04) ? 0x0000 : 0xffff;
			__ay_Channels[0].NoiseMask = (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value&0x08) ? 0x0000 : 0xffff;
			__ay_Channels[1].NoiseMask = (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value&0x10) ? 0x0000 : 0xffff;
			__ay_Channels[2].NoiseMask = (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value&0x20) ? 0x0000 : 0xffff;
		break;

#define SetVolume(n)\
	if(__ay_AudioBuffer[ __ay_AudioReadPtr ].Value&0x10)\
		__ay_Channels[n].VolPtr = &__ay_EnvelopeVolume;\
	else\
	{\
		__ay_Channels[n].VolPtr = &__ay_Channels[n].Volume;\
		__ay_Channels[n].Volume = __ay_Levels[__ay_AudioBuffer[ __ay_AudioReadPtr ].Value];\
	}

		case 8:		SetVolume(0); break;
		case 9:		SetVolume(1); break;
		case 10:	SetVolume(2); break;

		case 11:	__ay_EnvelopePitch = (__ay_EnvelopePitch&0xff00) | __ay_AudioBuffer[ __ay_AudioReadPtr ].Value; __ay_FixEnvelope(); break;
		case 12:	__ay_EnvelopePitch = (__ay_EnvelopePitch&0x00ff) | (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value << 8); __ay_FixEnvelope(); break;
		case 13:
			/*
				0, 9 - decay, off			- Tab1, 15 to 0
				4, 15 - attack, off			- Tab2, 15 to 0

				8 - repeated decay			- Tab1, 15 repeat
				10 - decay, attack, repeat	- Tab3, 15 repeat
				11 - decay, hold			- Tab4

				12 - repeated attack		- Tab2, 16 repeat
				14 - attack, decay, repeat	- Tab3, 31 repeat
				13 - attack, hold			- Tab5

				Tab1 = 0, 1, ... 15, [repeat]
				Tab2 = 0, 15 ... 1, [repeat]
				Tab3 = 0, 1 ...15, 15, ..., 0
			*/
				switch(__ay_AudioBuffer[ __ay_AudioReadPtr ].Value)
				{
					case 0: case 1: case 2: case 3: case 9:
						__ay_EnvelopeMask = 0xffffffff;
						__ay_EnvelopePattern = __ay_EnvelopePatterns[0];
						__ay_EnvelopePos = 15 << TONE_SHIFT;
					break;

					case 4: case 5: case 6: case 7: case 15:
						__ay_EnvelopeMask = 0xffffffff;
						__ay_EnvelopePattern = __ay_EnvelopePatterns[1];
						__ay_EnvelopePos = 15 << TONE_SHIFT;
					break;

					case 8: 
						__ay_EnvelopeMask = ~(~0xf << TONE_SHIFT);
						__ay_EnvelopePattern = __ay_EnvelopePatterns[0];
						__ay_EnvelopePos = 15 << TONE_SHIFT;
					break;
					case 12:
						__ay_EnvelopeMask = ~(~0x1f << TONE_SHIFT);
						__ay_EnvelopePattern = __ay_EnvelopePatterns[1];
						__ay_EnvelopePos = 16 << TONE_SHIFT;
					break;

					case 10:
						__ay_EnvelopeMask = ~(~0x1f << TONE_SHIFT);
						__ay_EnvelopePattern = __ay_EnvelopePatterns[2];
						__ay_EnvelopePos = 15 << TONE_SHIFT;
					break;
					case 14:
						__ay_EnvelopeMask = ~(~0x1f << TONE_SHIFT);
						__ay_EnvelopePattern = __ay_EnvelopePatterns[2];
						__ay_EnvelopePos = 31 << TONE_SHIFT;
					break;

					case 11:
						__ay_EnvelopeMask = 0xffffffff;
						__ay_EnvelopePattern = __ay_EnvelopePatterns[3];
						__ay_EnvelopePos = 16 << TONE_SHIFT;
					break;
					case 13:
						__ay_EnvelopeMask = 0xffffffff;
						__ay_EnvelopePattern = __ay_EnvelopePatterns[4];
						__ay_EnvelopePos = 16 << TONE_SHIFT;
					break;
				}

		case 16:
			__ay_SpeakerLevel = (__ay_AudioBuffer[ __ay_AudioReadPtr ].Value ? 64 : 0) << 8;
		break;
	}

	__ay_AudioReadPtr = (__ay_AudioReadPtr+1)&(CULA_AUDIOEVENT_LENGTH-1);
}

void __ay_AudioUpdateFunction(Uint16 *TargetBuffer, int TargetLength)
{
	int SPtr = 0;
	__ay_WrittenSamples += TargetLength;
	while(SPtr < TargetLength)
	{
		Uint32 SamplesToWrite;
		while(
			( __ay_AudioReadPtr != __ay_AudioWritePtr) &&
			( __ay_AudioProcessTime >= __ay_AudioBuffer[ __ay_AudioReadPtr ].SampleDiff)
		)
		{
			__ay_EnactAudioEvent();
			__ay_AudioProcessTime = 0;
		}

		if(__ay_AudioReadPtr == __ay_AudioWritePtr)
		{
			SamplesToWrite = TargetLength-SPtr;
		}
		else
		{
			SamplesToWrite = __ay_AudioBuffer[ __ay_AudioReadPtr ].SampleDiff - __ay_AudioProcessTime;

			if(SamplesToWrite > (unsigned)(TargetLength-SPtr))
				SamplesToWrite = TargetLength-SPtr;
		}

		/*
			BEHOLD: the clauseless AY sound emulation inner loop (*ahem* which works only
			if your computer allows signed shifts)
		*/

		__ay_AudioProcessTime += SamplesToWrite;
		while(SamplesToWrite--)
		{
#define Generator(n)\
		(\
			*__ay_Channels[n].VolPtr -\
			(\
				(((__ay_Channels[n].SamplePos&TONE_BIT) ? 0 : *__ay_Channels[n].VolPtr )&__ay_Channels[n].ToneMask) |\
				((__ay_NoiseWave[(__ay_NoisePos >> TONE_SHIFT)&1023] ? 0 : *__ay_Channels[n].VolPtr)&__ay_Channels[n].NoiseMask)\
			)\
		)

			__ay_EnvelopeVolume = __ay_EnvelopePattern[__ay_EnvelopePos >> TONE_SHIFT];
			TargetBuffer[SPtr++] = Generator(0) + Generator(1) + Generator(2);// + __ay_SpeakerLevel;

			__ay_Channels[0].SamplePos += __ay_Channels[0].SampleAdd;
			__ay_Channels[1].SamplePos += __ay_Channels[1].SampleAdd;
			__ay_Channels[2].SamplePos += __ay_Channels[2].SampleAdd;
			__ay_NoisePos += __ay_NoiseAdd;
			__ay_EnvelopePos = (__ay_EnvelopePos-__ay_EnvelopeAdd)&__ay_EnvelopeMask;
			__ay_EnvelopePos &= ~(__ay_EnvelopePos >> 31);
		}
	}
#undef Generator
}

void __ay_Update(Uint32 cycles)
{
	void *b;
	
	if(!cycles) return;

	__ay_TotalTime += cycles;
	__ay_SampleComparitor += cycles;

	if(__ay_AudioEnabled)
	{
		int WrittenTime;

		// force resync if samplediff reveals more than an eighth of a second
		Uint32 OldTime = __ay_AudioBuffer[__ay_AudioReadPtr].ClockTime;
		Uint32 BigDiff = __ay_TotalTime - OldTime;

		// generate NOP event to keep sound system alert and awake
		__ay_AudioBuffer[__ay_AudioWritePtr].Register = 17;
		__ay_WriteAudioEvent(__ay_TotalTime);

		BigDiff = (BigDiff * SAMPLE_RATE) / CLOCK_RATE;
		if(BigDiff > (__ay_CurrentBufferSize << 1))
		{
			printf(".");
			while(BigDiff > __ay_CurrentBufferSize && __ay_AudioReadPtr != __ay_AudioWritePtr)
			{
				__ay_EnactAudioEvent();
				BigDiff = __ay_TotalTime - __ay_AudioBuffer[__ay_AudioReadPtr].ClockTime;
				BigDiff = (BigDiff * SAMPLE_RATE) / CLOCK_RATE;
			}
		}

		if(b = get_audio_stream_buffer(__ay_audiostream))
		{
			__ay_AudioUpdateFunction((Uint16 *)b, __ay_CurrentBufferSize);
			free_audio_stream_buffer(__ay_audiostream);
		}

		// check if the audio buffer is too small
		WrittenTime = (int)(((float)__ay_SampleComparitor) * SAMPLE_RATE / CLOCK_RATE);
		if(WrittenTime > __ay_CurrentBufferSize*4 && abs( WrittenTime - __ay_WrittenSamples) > __ay_CurrentBufferSize*4)
		{
			__ay_DoubleBufferSize();
			__ay_SampleComparitor = __ay_WrittenSamples = 0;
		}
		else
		{
			// assume all times over 4096 are a temporary measure
			if(WrittenTime > __ay_CurrentBufferSize*4 && __ay_CurrentBufferSize > 4096)
			{
				__ay_HalveBufferSize();
				__ay_SampleComparitor = __ay_WrittenSamples = 0;
			}

			// stop things from overflowing and messing up
			if(WrittenTime > SAMPLE_RATE*4)
			{
				__ay_SampleComparitor -= CLOCK_RATE*4;
				__ay_WrittenSamples -= SAMPLE_RATE*4;
			}
		}
	}
}

/*

	PSG playback code. It's a very stupid file format, so this is very simple code

*/

FILE *__psg_file = NULL;
int __psg_framewait, __psg_loop;

int psg_OpenFile(const char *name, int loop)
{
	unsigned char Header[5];

	psg_CloseFile();
	__psg_loop = loop;
	if(!(__psg_file = fopen(name, "rb")))
		return FALSE;

	fread(Header, 1, 4, __psg_file); Header[4] = '\0';
	if(Header[0] != 'P' || Header[1] != 'S' || Header[2] != 'G' || Header[3] != 0x1a)
	{
		fclose(__psg_file); __psg_file = NULL;
		return FALSE;
	}

	__psg_framewait = 0;
	return TRUE;
}

void psg_CloseFile(void)
{
	ay_Write(AY_REGSELECT, 7);
	ay_Write(AY_REGVALUE, 0xff);
	if(__psg_file)
	{
		fclose(__psg_file);
		__psg_file = NULL;
	}
}

int psg_Update(void)
{
	Uint8 Command;

	if(!__psg_file)
		return FALSE;

	if(__psg_framewait)
	{
		__psg_framewait--;
		return TRUE;
	}

	while(1)
	{
		Command = fgetc(__psg_file);
		if(feof(__psg_file) || Command == 0xfd)
		{
			if(__psg_loop)
			{
				fseek(__psg_file, 4, SEEK_SET);
				Command = fgetc(__psg_file);
			}
			else
			{
				psg_CloseFile();
				return FALSE;
			}
		}
		if(Command == 0xff) return TRUE;
		if(Command == 0xfe)
		{
			__psg_framewait = fgetc(__psg_file);
			return TRUE;
		}
		ay_Write(AY_REGSELECT, Command);
		ay_Write(AY_REGVALUE, (Uint8)fgetc(__psg_file));
	}
}
