#include "retrohack.h" #include #include 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 1024 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; 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(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(BUFFER_SIZE, 16, FALSE, SAMPLE_RATE, 128, 128); } 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; 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; __ay_TotalTime += cycles; if(__ay_AudioEnabled) { // force resync if samplediff reveals more than an eighth of a second Uint32 OldTime = __ay_AudioBuffer[__ay_AudioReadPtr&(CULA_AUDIOEVENT_LENGTH-1)].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); while(BigDiff > (CLOCK_RATE >> 3)) { __ay_EnactAudioEvent(); BigDiff = __ay_TotalTime - __ay_AudioBuffer[__ay_AudioReadPtr&(CULA_AUDIOEVENT_LENGTH-1)].ClockTime; } if(b = get_audio_stream_buffer(__ay_audiostream)) { __ay_AudioUpdateFunction((Uint16 *)b, BUFFER_SIZE); free_audio_stream_buffer(__ay_audiostream); } } } /* 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)); } }