/* ** $Id:$ ** ** File: psg.c -- software implementation of AY-3-8910 Programmable ** sound generator. ** ** Based on: Sound.c, (C) Ville Hallik (ville@physic.ut.ee) 1996 ** ** SCC emulation removed. Made modular and capable of multiple PSG ** emulation. ** ** Modifications (C) 1996 Michael Cuddy, Fen's Ende Software. ** http://www.fensende.com/Users/mcuddy ** */ #include #include #include "psg.h" /* ** some globals ... */ static int AYSoundRate; /* Output rate (Hz) */ static int AYBufSize; /* size of sound buffer, in samples */ static int AYNumChips; /* total # of PSG's emulated */ static AY8910 *AYPSG; /* array of PSG's */ static int _AYInitChip(int num, SAMPLE *buf); static void _AYFreeChip(int num); /* ** Initialize AY8910 emulator(s). ** ** 'num' is the number of virtual AY8910's to allocate ** 'rate' is sampling rate and 'bufsiz' is the size of the ** buffer that should be updated at each interval */ int AYInit(int num, int rate, int bufsiz, ... ) { int i; va_list ap; SAMPLE *userbuffer = 0; int moreargs = 1; va_start(ap,bufsiz); if (AYPSG) return (-1); /* duplicate init. */ AYNumChips = num; AYSoundRate = rate; AYBufSize = bufsiz; AYPSG = (AY8910 *)malloc(sizeof(AY8910) * AYNumChips); if (AYPSG == NULL) return (0); for ( i = 0 ; i < AYNumChips; i++ ) { if (moreargs) userbuffer = va_arg(ap,SAMPLE*); if (userbuffer == NULL) moreargs = 0; if (_AYInitChip(i,userbuffer) < 0) { int j; for ( j = 0 ; j < i ; j ++ ) { _AYFreeChip(j); } return(-1); } } return(0); } void AYShutdown() { int i; if (!AYPSG) return; for ( i = 0 ; i < AYNumChips ; i++ ) { _AYFreeChip(i); } free(AYPSG); AYPSG = NULL; AYSoundRate = AYBufSize = 0; } /* ** reset all chip registers. */ void AYResetChip(int num) { int i; AY8910 *PSG = &(AYPSG[num]); memset(PSG->Buf,'\0',AYBufSize); /* initialize hardware registers */ for( i=0; i<16; i++ ) PSG->Regs[i] = AUDIO_CONV(0); PSG->Regs[AY_ENABLE] = 077; PSG->Regs[AY_AVOL] = 8; PSG->Regs[AY_BVOL] = 8; PSG->Regs[AY_CVOL] = 8; PSG->NoiseGen = 1; PSG->Envelope = 15; PSG->StateNoise = 0; PSG->Incr0= PSG->Incr1 = PSG->Incr2 = PSG->Increnv = PSG->Incrnoise = 0; } /* ** allocate buffers and clear registers for one of the emulated ** AY8910 chips. */ static int _AYInitChip(int num, SAMPLE *buf) { AY8910 *PSG = &(AYPSG[num]); PSG->UserBuffer = 0; if (buf) { PSG->Buf = buf; PSG->UserBuffer = 1; } else { if ((PSG->Buf=(SAMPLE *)malloc(AYBufSize))==NULL) return -1; } PSG->Port[0] = PSG->Port[1] = NULL; AYResetChip(num); return 0; } /* ** release storage for a chip */ static void _AYFreeChip(int num) { AY8910 *PSG = &(AYPSG[num]); if (PSG->Buf && !PSG->UserBuffer) free(PSG->Buf); PSG->Buf = NULL; } static unsigned char _AYEnvForms[16][32] = { { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }, { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; /* write a register on AY8910 chip number 'n' */ void AYWriteReg(int n, int r, int v) { AY8910 *PSG = &(AYPSG[n]); PSG->Regs[r] = v; switch( r ) { case AY_AVOL: case AY_BVOL: case AY_CVOL: PSG->Regs[r] &= 0x1F; /* mask volume */ break; case AY_EFINE: case AY_ECOARSE: PSG->Countenv=0; /* fall through */ case AY_ESHAPE: PSG->Regs[AY_ESHAPE] &= 0xF; break; case AY_PORTA: if (PSG->Port[0]) { (PSG->Port[0])(PSG,AY_PORTA,1,v); } break; case AY_PORTB: if (PSG->Port[1]) { (PSG->Port[1])(PSG,AY_PORTB,1,v); } break; } } byte AYReadReg(int n, int r) { AY8910 *PSG = &(AYPSG[n]); switch (r) { case AY_PORTA: if (PSG->Port[0]) { (PSG->Port[0])(PSG,AY_PORTA,0,0); } break; case AY_PORTB: if (PSG->Port[1]) { (PSG->Port[1])(PSG,AY_PORTB,0,0); } break; } return PSG->Regs[r]; } static void _AYUpdateChip(int num) { AY8910 *PSG = &(AYPSG[num]); int i, x; int c0, c1, l0, l1, l2; x = (PSG->Regs[AY_AFINE]+((unsigned)(PSG->Regs[AY_ACOARSE]&0xF)<<8)); PSG->Incr0 = x ? AY8910_CLOCK / AYSoundRate * 4 / x : 0; x = (PSG->Regs[AY_BFINE]+((unsigned)(PSG->Regs[AY_BCOARSE]&0xF)<<8)); PSG->Incr1 = x ? AY8910_CLOCK / AYSoundRate * 4 / x : 0; x = (PSG->Regs[AY_CFINE]+((unsigned)(PSG->Regs[AY_CCOARSE]&0xF)<<8)); PSG->Incr2 = x ? AY8910_CLOCK / AYSoundRate * 4 / x : 0; x = PSG->Regs[AY_NOISEPER]&0x1F; PSG->Incrnoise = AY8910_CLOCK / AYSoundRate * 4 / ( x ? x : 1 ); x = (PSG->Regs[AY_EFINE]+((unsigned)PSG->Regs[AY_ECOARSE]<<8)); PSG->Increnv = x ? AY8910_CLOCK / AYSoundRate * 4 / x * AYBufSize : 0; PSG->Envelope = _AYEnvForms[PSG->Regs[AY_ESHAPE]][(PSG->Countenv>>16)&0x1F]; if( ( PSG->Countenv += PSG->Increnv ) & 0xFFE00000 ) { switch( PSG->Regs[AY_ESHAPE] ) { case 8: case 10: case 12: case 14: PSG->Countenv -= 0x200000; break; default: PSG->Countenv = 0x100000; PSG->Increnv = 0; } } PSG->Vol0 = ( PSG->Regs[AY_AVOL] < 16 ) ? PSG->Regs[AY_AVOL] : PSG->Envelope; PSG->Vol1 = ( PSG->Regs[AY_BVOL] < 16 ) ? PSG->Regs[AY_BVOL] : PSG->Envelope; PSG->Vol2 = ( PSG->Regs[AY_CVOL] < 16 ) ? PSG->Regs[AY_CVOL] : PSG->Envelope; PSG->Volnoise = ( ( ( PSG->Regs[AY_ENABLE] & 010 ) ? 0 : PSG->Vol0 ) + ( ( PSG->Regs[AY_ENABLE] & 020 ) ? 0 : PSG->Vol1 ) + ( ( PSG->Regs[AY_ENABLE] & 040 ) ? 0 : PSG->Vol2 ) ) / 2; PSG->Vol0 = ( PSG->Regs[AY_ENABLE] & 001 ) ? 0 : PSG->Vol0; PSG->Vol1 = ( PSG->Regs[AY_ENABLE] & 002 ) ? 0 : PSG->Vol1; PSG->Vol2 = ( PSG->Regs[AY_ENABLE] & 004 ) ? 0 : PSG->Vol2; for( i=0; iCounter0; c1 = PSG->Counter0 + PSG->Incr0; l0 = ((c0&0x8000)?-16:16); if( (c0^c1)&0x8000 ) { l0=l0*(0x8000-(c0&0x7FFF)-(c1&0x7FFF))/PSG->Incr0; } PSG->Counter0 = c1 & 0xFFFF; c0 = PSG->Counter1; c1 = PSG->Counter1 + PSG->Incr1; l1 = ((c0&0x8000)?-16:16); if( (c0^c1)&0x8000 ) { l1=l1*(0x8000-(c0&0x7FFF)-(c1&0x7FFF))/PSG->Incr1; } PSG->Counter1 = c1 & 0xFFFF; c0 = PSG->Counter2; c1 = PSG->Counter2 + PSG->Incr2; l2 = ((c0&0x8000)?-16:16); if( (c0^c1)&0x8000 ) { l2=l2*(0x8000-(c0&0x7FFF)-(c1&0x7FFF))/PSG->Incr2; } PSG->Counter2 = c1 & 0xFFFF; PSG->Countnoise &= 0xFFFF; if( ( PSG->Countnoise += PSG->Incrnoise ) & 0xFFFF0000 ) { /* ** The following code is a random bit generator :) */ PSG->StateNoise = ( ( PSG->NoiseGen <<= 1 ) & 0x80000000 ? PSG->NoiseGen ^= 0x00040001 : PSG->NoiseGen ) & 1; } PSG->Buf[i] = AUDIO_CONV ( ( l0 * PSG->Vol0 + l1 * PSG->Vol1 + l2 * PSG->Vol2 ) / 6 + ( PSG->StateNoise ? PSG->Volnoise : -PSG->Volnoise ) ); } } /* ** called to update all chips */ void AYUpdate(void) { int i; for ( i = 0 ; i < AYNumChips; i++ ) { _AYUpdateChip(i); } } /* ** return the buffer into which AYUpdate() has just written it's sample ** data */ SAMPLE *AYBuffer(int n) { return AYPSG[n].Buf; } void AYSetBuffer(int n, SAMPLE *buf) { AYPSG[n].Buf = buf; } /* ** set a port handler function to be called when AYWriteReg() or AYReadReg() ** is called for register AY_PORTA or AY_PORTB. ** */ void AYSetPortHandler(int n, int port, AYPortHandler func) { port -= AY_PORTA; if (port > 1 || port < 0 ) return; AYPSG[n].Port[port] = func; } /* ** Change Log ** ---------- ** $Log:$ */