-/***************************************************************************\r
-\r
- ay8910.cpp\r
-\r
- Emulation of the AY-3-8910 / YM2149 sound chip.\r
-\r
- Based on various code snippets by Ville Hallik, Michael Cuddy,\r
- Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria.\r
-\r
-***************************************************************************/\r
-\r
-// \r
-// From mame.txt (http://www.mame.net/readme.html)\r
-// \r
-// VI. Reuse of Source Code\r
-// --------------------------\r
-// This chapter might not apply to specific portions of MAME (e.g. CPU\r
-// emulators) which bear different copyright notices.\r
-// The source code cannot be used in a commercial product without the written\r
-// authorization of the authors. Use in non-commercial products is allowed, and\r
-// indeed encouraged. If you use portions of the MAME source code in your\r
-// program, however, you must make the full source code freely available as\r
-// well.\r
-// Usage of the _information_ contained in the source code is free for any use.\r
-// However, given the amount of time and energy it took to collect this\r
-// information, if you find new information we would appreciate if you made it\r
-// freely available as well.\r
-// \r
-\r
-// JLH: Commented out MAME specific crap\r
-\r
-#include <string.h> // for memset()\r
-#include "ay8910.h"\r
-\r
-#define MAX_OUTPUT 0x7FFF\r
-\r
-// See AY8910_set_clock() for definition of STEP\r
-#define STEP 0x8000\r
-\r
-struct AY8910\r
-{\r
- int Channel;\r
- int SampleRate;\r
-// mem_read_handler PortAread;\r
-// mem_read_handler PortBread;\r
-// mem_write_handler PortAwrite;\r
-// mem_write_handler PortBwrite;\r
- int register_latch;\r
- unsigned char Regs[16];\r
- int lastEnable;\r
- unsigned int UpdateStep;\r
- int PeriodA,PeriodB,PeriodC,PeriodN,PeriodE;\r
- int CountA,CountB,CountC,CountN,CountE;\r
- unsigned int VolA,VolB,VolC,VolE;\r
- unsigned char EnvelopeA,EnvelopeB,EnvelopeC;\r
- unsigned char OutputA,OutputB,OutputC,OutputN;\r
- signed char CountEnv;\r
- unsigned char Hold,Alternate,Attack,Holding;\r
- int RNG;\r
- unsigned int VolTable[32];\r
-};\r
-\r
-/* register id's */\r
-#define AY_AFINE (0)\r
-#define AY_ACOARSE (1)\r
-#define AY_BFINE (2)\r
-#define AY_BCOARSE (3)\r
-#define AY_CFINE (4)\r
-#define AY_CCOARSE (5)\r
-#define AY_NOISEPER (6)\r
-#define AY_ENABLE (7)\r
-#define AY_AVOL (8)\r
-#define AY_BVOL (9)\r
-#define AY_CVOL (10)\r
-#define AY_EFINE (11)\r
-#define AY_ECOARSE (12)\r
-#define AY_ESHAPE (13)\r
-\r
-#define AY_PORTA (14)\r
-#define AY_PORTB (15)\r
-\r
-\r
-static struct AY8910 AYPSG[MAX_8910]; /* array of PSG's */\r
-\r
-\r
-void _AYWriteReg(int n, int r, int v)\r
-{\r
- struct AY8910 *PSG = &AYPSG[n];\r
- int old;\r
-\r
- PSG->Regs[r] = v;\r
-\r
- /* A note about the period of tones, noise and envelope: for speed reasons, *\r
- * we count down from the period to 0, but careful studies of the chip * \r
- * output prove that it instead counts up from 0 until the counter becomes * \r
- * greater or equal to the period. This is an important difference when the * \r
- * program is rapidly changing the period to modulate the sound. * \r
- * To compensate for the difference, when the period is changed we adjust * \r
- * our internal counter. * \r
- * Also, note that period = 0 is the same as period = 1. This is mentioned * \r
- * in the YM2203 data sheets. However, this does NOT apply to the Envelope * \r
- * period. In that case, period = 0 is half as period = 1. */\r
- switch (r)\r
- {\r
- case AY_AFINE:\r
- case AY_ACOARSE:\r
- PSG->Regs[AY_ACOARSE] &= 0x0F;\r
- old = PSG->PeriodA;\r
- PSG->PeriodA = (PSG->Regs[AY_AFINE] + 256 * PSG->Regs[AY_ACOARSE]) * PSG->UpdateStep;\r
-\r
- if (PSG->PeriodA == 0) PSG->PeriodA = PSG->UpdateStep;\r
-\r
- PSG->CountA += PSG->PeriodA - old;\r
-\r
- if (PSG->CountA <= 0) PSG->CountA = 1;\r
- break;\r
- case AY_BFINE:\r
- case AY_BCOARSE:\r
- PSG->Regs[AY_BCOARSE] &= 0x0F;\r
- old = PSG->PeriodB;\r
- PSG->PeriodB = (PSG->Regs[AY_BFINE] + 256 * PSG->Regs[AY_BCOARSE]) * PSG->UpdateStep;\r
-\r
- if (PSG->PeriodB == 0) PSG->PeriodB = PSG->UpdateStep;\r
-\r
- PSG->CountB += PSG->PeriodB - old;\r
-\r
- if (PSG->CountB <= 0) PSG->CountB = 1;\r
- break;\r
- case AY_CFINE:\r
- case AY_CCOARSE:\r
- PSG->Regs[AY_CCOARSE] &= 0x0F;\r
- old = PSG->PeriodC;\r
- PSG->PeriodC = (PSG->Regs[AY_CFINE] + 256 * PSG->Regs[AY_CCOARSE]) * PSG->UpdateStep;\r
-\r
- if (PSG->PeriodC == 0) PSG->PeriodC = PSG->UpdateStep;\r
-\r
- PSG->CountC += PSG->PeriodC - old;\r
-\r
- if (PSG->CountC <= 0) PSG->CountC = 1;\r
- break;\r
- case AY_NOISEPER:\r
- PSG->Regs[AY_NOISEPER] &= 0x1F;\r
- old = PSG->PeriodN;\r
- PSG->PeriodN = PSG->Regs[AY_NOISEPER] * PSG->UpdateStep;\r
-\r
- if (PSG->PeriodN == 0) PSG->PeriodN = PSG->UpdateStep;\r
-\r
- PSG->CountN += PSG->PeriodN - old;\r
-\r
- if (PSG->CountN <= 0) PSG->CountN = 1;\r
- break;\r
- case AY_ENABLE:\r
- if ((PSG->lastEnable == -1) ||\r
- ((PSG->lastEnable & 0x40) != (PSG->Regs[AY_ENABLE] & 0x40)))\r
- {\r
- /* write out 0xff if port set to input */\r
-// if (PSG->PortAwrite)\r
-// (*PSG->PortAwrite)(0, (UINT8) ((PSG->Regs[AY_ENABLE] & 0x40) ? PSG->Regs[AY_PORTA] : 0xff)); // [TC: UINT8 cast]\r
- }\r
-\r
- if ((PSG->lastEnable == -1) ||\r
- ((PSG->lastEnable & 0x80) != (PSG->Regs[AY_ENABLE] & 0x80)))\r
- {\r
- /* write out 0xff if port set to input */\r
-// if (PSG->PortBwrite)\r
-// (*PSG->PortBwrite)(0, (UINT8) ((PSG->Regs[AY_ENABLE] & 0x80) ? PSG->Regs[AY_PORTB] : 0xff)); // [TC: UINT8 cast]\r
- }\r
-\r
- PSG->lastEnable = PSG->Regs[AY_ENABLE];\r
- break;\r
- case AY_AVOL:\r
- PSG->Regs[AY_AVOL] &= 0x1F;\r
- PSG->EnvelopeA = PSG->Regs[AY_AVOL] & 0x10;\r
- PSG->VolA = PSG->EnvelopeA ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_AVOL] ? PSG->Regs[AY_AVOL]*2+1 : 0];\r
- break;\r
- case AY_BVOL:\r
- PSG->Regs[AY_BVOL] &= 0x1F;\r
- PSG->EnvelopeB = PSG->Regs[AY_BVOL] & 0x10;\r
- PSG->VolB = PSG->EnvelopeB ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_BVOL] ? PSG->Regs[AY_BVOL]*2+1 : 0];\r
- break;\r
- case AY_CVOL:\r
- PSG->Regs[AY_CVOL] &= 0x1F;\r
- PSG->EnvelopeC = PSG->Regs[AY_CVOL] & 0x10;\r
- PSG->VolC = PSG->EnvelopeC ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_CVOL] ? PSG->Regs[AY_CVOL]*2+1 : 0];\r
- break;\r
- case AY_EFINE:\r
- case AY_ECOARSE:\r
- old = PSG->PeriodE;\r
- PSG->PeriodE = ((PSG->Regs[AY_EFINE] + 256 * PSG->Regs[AY_ECOARSE])) * PSG->UpdateStep;\r
-\r
- if (PSG->PeriodE == 0) PSG->PeriodE = PSG->UpdateStep / 2;\r
-\r
- PSG->CountE += PSG->PeriodE - old;\r
-\r
- if (PSG->CountE <= 0) PSG->CountE = 1;\r
- break;\r
- case AY_ESHAPE:\r
- /* envelope shapes:\r
- C AtAlH\r
- 0 0 x x \___\r
-\r
- 0 1 x x /___\r
-\r
- 1 0 0 0 \\\\\r
-\r
- 1 0 0 1 \___\r
-\r
- 1 0 1 0 \/\/\r
- ___\r
- 1 0 1 1 \\r
-\r
- 1 1 0 0 ////\r
- ___\r
- 1 1 0 1 /\r
-\r
- 1 1 1 0 /\/\\r
-\r
- 1 1 1 1 /___\r
-\r
- The envelope counter on the AY-3-8910 has 16 steps. On the YM2149 it\r
- has twice the steps, happening twice as fast. Since the end result is\r
- just a smoother curve, we always use the YM2149 behaviour.\r
- */\r
- PSG->Regs[AY_ESHAPE] &= 0x0F;\r
- PSG->Attack = (PSG->Regs[AY_ESHAPE] & 0x04) ? 0x1F : 0x00;\r
-\r
- if ((PSG->Regs[AY_ESHAPE] & 0x08) == 0)\r
- {\r
- /* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */\r
- PSG->Hold = 1;\r
- PSG->Alternate = PSG->Attack;\r
- }\r
- else\r
- {\r
- PSG->Hold = PSG->Regs[AY_ESHAPE] & 0x01;\r
- PSG->Alternate = PSG->Regs[AY_ESHAPE] & 0x02;\r
- }\r
-\r
- PSG->CountE = PSG->PeriodE;\r
- PSG->CountEnv = 0x1F;\r
- PSG->Holding = 0;\r
- PSG->VolE = PSG->VolTable[PSG->CountEnv ^ PSG->Attack];\r
-\r
- if (PSG->EnvelopeA) PSG->VolA = PSG->VolE;\r
- if (PSG->EnvelopeB) PSG->VolB = PSG->VolE;\r
- if (PSG->EnvelopeC) PSG->VolC = PSG->VolE;\r
- break;\r
- case AY_PORTA:\r
- if (PSG->Regs[AY_ENABLE] & 0x40)\r
- {\r
-// if (PSG->PortAwrite)\r
-// (*PSG->PortAwrite)(0, PSG->Regs[AY_PORTA]);\r
-// else\r
-// logerror("PC %04x: warning - write %02x to 8910 #%d Port A\n",activecpu_get_pc(),PSG->Regs[AY_PORTA],n);\r
- }\r
- else\r
- {\r
-// logerror("warning: write to 8910 #%d Port A set as input - ignored\n",n);\r
- }\r
- break;\r
- case AY_PORTB:\r
- if (PSG->Regs[AY_ENABLE] & 0x80)\r
- {\r
-// if (PSG->PortBwrite)\r
-// (*PSG->PortBwrite)(0, PSG->Regs[AY_PORTB]);\r
-// else\r
-// logerror("PC %04x: warning - write %02x to 8910 #%d Port B\n",activecpu_get_pc(),PSG->Regs[AY_PORTB],n);\r
- }\r
- else\r
- {\r
-// logerror("warning: write to 8910 #%d Port B set as input - ignored\n",n);\r
- }\r
- break;\r
- }\r
-}\r
-\r
-\r
-// /length/ is the number of samples we require\r
-// NB. This should be called at twice the 6522 IRQ rate or (eg) 60Hz if no IRQ.\r
-void AY8910Update(int chip, int16 ** buffer, int length) // [TC: Removed static]\r
-{\r
- struct AY8910 * PSG = &AYPSG[chip];\r
- INT16 * buf1, * buf2, * buf3;\r
- int outn;\r
-\r
- buf1 = buffer[0];\r
- buf2 = buffer[1];\r
- buf3 = buffer[2];\r
-\r
- /* The 8910 has three outputs, each output is the mix of one of the three *\r
- * tone generators and of the (single) noise generator. The two are mixed *\r
- * BEFORE going into the DAC. The formula to mix each channel is: *\r
- * (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). *\r
- * Note that this means that if both tone and noise are disabled, the output *\r
- * is 1, not 0, and can be modulated changing the volume. *\r
- * *\r
- * If the channels are disabled, set their output to 1, and increase the *\r
- * counter, if necessary, so they will not be inverted during this update. *\r
- * Setting the output to 1 is necessary because a disabled channel is locked *\r
- * into the ON state (see above); and it has no effect if the volume is 0. *\r
- * If the volume is 0, increase the counter, but don't touch the output. */\r
- if (PSG->Regs[AY_ENABLE] & 0x01)\r
- {\r
- if (PSG->CountA <= length * STEP) PSG->CountA += length * STEP;\r
- PSG->OutputA = 1;\r
- }\r
- else if (PSG->Regs[AY_AVOL] == 0)\r
- {\r
- /* note that I do count += length, NOT count = length + 1. You might think *\r
- * it's the same since the volume is 0, but doing the latter could cause *\r
- * interferencies when the program is rapidly modulating the volume. */\r
- if (PSG->CountA <= length * STEP) PSG->CountA += length * STEP;\r
- }\r
-\r
- if (PSG->Regs[AY_ENABLE] & 0x02)\r
- {\r
- if (PSG->CountB <= length * STEP) PSG->CountB += length * STEP;\r
- PSG->OutputB = 1;\r
- }\r
- else if (PSG->Regs[AY_BVOL] == 0)\r
- {\r
- if (PSG->CountB <= length * STEP) PSG->CountB += length * STEP;\r
- }\r
-\r
- if (PSG->Regs[AY_ENABLE] & 0x04)\r
- {\r
- if (PSG->CountC <= length * STEP) PSG->CountC += length * STEP;\r
- PSG->OutputC = 1;\r
- }\r
- else if (PSG->Regs[AY_CVOL] == 0)\r
- {\r
- if (PSG->CountC <= length * STEP) PSG->CountC += length * STEP;\r
- }\r
-\r
- /* for the noise channel we must not touch OutputN - it's also not necessary *\r
- * since we use outn. */\r
- if ((PSG->Regs[AY_ENABLE] & 0x38) == 0x38) /* all off */\r
- if (PSG->CountN <= length * STEP) PSG->CountN += length * STEP;\r
-\r
- outn = (PSG->OutputN | PSG->Regs[AY_ENABLE]);\r
-\r
- /* buffering loop */\r
- while (length)\r
- {\r
- int vola, volb, volc;\r
- int left;\r
-\r
- /* vola, volb and volc keep track of how long each square wave stays *\r
- * in the 1 position during the sample period. */\r
- vola = volb = volc = 0;\r
-\r
- left = STEP;\r
- do\r
- {\r
- int nextevent;\r
-\r
- if (PSG->CountN < left) nextevent = PSG->CountN;\r
- else nextevent = left;\r
-\r
- if (outn & 0x08)\r
- {\r
- if (PSG->OutputA) vola += PSG->CountA;\r
-\r
- PSG->CountA -= nextevent;\r
- /* PeriodA is the half period of the square wave. Here, in each *\r
- * loop I add PeriodA twice, so that at the end of the loop the *\r
- * square wave is in the same status (0 or 1) it was at the start. *\r
- * vola is also incremented by PeriodA, since the wave has been 1 *\r
- * exactly half of the time, regardless of the initial position. *\r
- * If we exit the loop in the middle, OutputA has to be inverted *\r
- * and vola incremented only if the exit status of the square *\r
- * wave is 1. */\r
- while (PSG->CountA <= 0)\r
- {\r
- PSG->CountA += PSG->PeriodA;\r
-\r
- if (PSG->CountA > 0)\r
- {\r
- PSG->OutputA ^= 1;\r
-\r
- if (PSG->OutputA) vola += PSG->PeriodA;\r
- break;\r
- }\r
-\r
- PSG->CountA += PSG->PeriodA;\r
- vola += PSG->PeriodA;\r
- }\r
-\r
- if (PSG->OutputA) vola -= PSG->CountA;\r
- }\r
- else\r
- {\r
- PSG->CountA -= nextevent;\r
- while (PSG->CountA <= 0)\r
- {\r
- PSG->CountA += PSG->PeriodA;\r
-\r
- if (PSG->CountA > 0)\r
- {\r
- PSG->OutputA ^= 1;\r
- break;\r
- }\r
-\r
- PSG->CountA += PSG->PeriodA;\r
- }\r
- }\r
-\r
- if (outn & 0x10)\r
- {\r
- if (PSG->OutputB) volb += PSG->CountB;\r
-\r
- PSG->CountB -= nextevent;\r
-\r
- while (PSG->CountB <= 0)\r
- {\r
- PSG->CountB += PSG->PeriodB;\r
-\r
- if (PSG->CountB > 0)\r
- {\r
- PSG->OutputB ^= 1;\r
-\r
- if (PSG->OutputB) volb += PSG->PeriodB;\r
- break;\r
- }\r
-\r
- PSG->CountB += PSG->PeriodB;\r
- volb += PSG->PeriodB;\r
- }\r
-\r
- if (PSG->OutputB) volb -= PSG->CountB;\r
- }\r
- else\r
- {\r
- PSG->CountB -= nextevent;\r
-\r
- while (PSG->CountB <= 0)\r
- {\r
- PSG->CountB += PSG->PeriodB;\r
-\r
- if (PSG->CountB > 0)\r
- {\r
- PSG->OutputB ^= 1;\r
- break;\r
- }\r
-\r
- PSG->CountB += PSG->PeriodB;\r
- }\r
- }\r
-\r
- if (outn & 0x20)\r
- {\r
- if (PSG->OutputC) volc += PSG->CountC;\r
-\r
- PSG->CountC -= nextevent;\r
-\r
- while (PSG->CountC <= 0)\r
- {\r
- PSG->CountC += PSG->PeriodC;\r
-\r
- if (PSG->CountC > 0)\r
- {\r
- PSG->OutputC ^= 1;\r
-\r
- if (PSG->OutputC) volc += PSG->PeriodC;\r
- break;\r
- }\r
-\r
- PSG->CountC += PSG->PeriodC;\r
- volc += PSG->PeriodC;\r
- }\r
-\r
- if (PSG->OutputC) volc -= PSG->CountC;\r
- }\r
- else\r
- {\r
- PSG->CountC -= nextevent;\r
-\r
- while (PSG->CountC <= 0)\r
- {\r
- PSG->CountC += PSG->PeriodC;\r
-\r
- if (PSG->CountC > 0)\r
- {\r
- PSG->OutputC ^= 1;\r
- break;\r
- }\r
-\r
- PSG->CountC += PSG->PeriodC;\r
- }\r
- }\r
-\r
- PSG->CountN -= nextevent;\r
-\r
- if (PSG->CountN <= 0)\r
- {\r
- /* Is noise output going to change? */\r
- if ((PSG->RNG + 1) & 0x00002) /* (bit0^bit1)? */\r
- {\r
- PSG->OutputN = ~PSG->OutputN;\r
- outn = (PSG->OutputN | PSG->Regs[AY_ENABLE]);\r
- }\r
-\r
- /* The Random Number Generator of the 8910 is a 17-bit shift *\r
- * register. The input to the shift register is bit0 XOR bit3 *\r
- * (bit0 is the output). This was verified on AY-3-8910 and *\r
- * YM2149 chips. *\r
- * *\r
- * The following is a fast way to compute bit17 = bit0^bit3. *\r
- * Instead of doing all the logic operations, we only check *\r
- * bit0, relying on the fact that after three shifts of the *\r
- * register, what now is bit3 will become bit0, and will *\r
- * invert, if necessary, bit14, which previously was bit17. */\r
- if (PSG->RNG & 0x00001)\r
- PSG->RNG ^= 0x24000; /* This version is called the "Galois configuration". */\r
-\r
- PSG->RNG >>= 1;\r
- PSG->CountN += PSG->PeriodN;\r
- }\r
-\r
- left -= nextevent;\r
- }\r
- while (left > 0);\r
-\r
- /* update envelope */\r
- if (PSG->Holding == 0)\r
- {\r
- PSG->CountE -= STEP;\r
-\r
- if (PSG->CountE <= 0)\r
- {\r
- do\r
- {\r
- PSG->CountEnv--;\r
- PSG->CountE += PSG->PeriodE;\r
- }\r
- while (PSG->CountE <= 0);\r
-\r
- /* check envelope current position */\r
- if (PSG->CountEnv < 0)\r
- {\r
- if (PSG->Hold)\r
- {\r
- if (PSG->Alternate)\r
- PSG->Attack ^= 0x1F;\r
-\r
- PSG->Holding = 1;\r
- PSG->CountEnv = 0;\r
- }\r
- else\r
- {\r
- /* if CountEnv has looped an odd number of times (usually 1), *\r
- * invert the output. */\r
- if (PSG->Alternate && (PSG->CountEnv & 0x20))\r
- PSG->Attack ^= 0x1F;\r
-\r
- PSG->CountEnv &= 0x1F;\r
- }\r
- }\r
-\r
- PSG->VolE = PSG->VolTable[PSG->CountEnv ^ PSG->Attack];\r
- /* reload volume */\r
- if (PSG->EnvelopeA) PSG->VolA = PSG->VolE;\r
- if (PSG->EnvelopeB) PSG->VolB = PSG->VolE;\r
- if (PSG->EnvelopeC) PSG->VolC = PSG->VolE;\r
- }\r
- }\r
-\r
-#if 0\r
- *(buf1++) = (vola * PSG->VolA) / STEP;\r
- *(buf2++) = (volb * PSG->VolB) / STEP;\r
- *(buf3++) = (volc * PSG->VolC) / STEP;\r
-#else // [Tom's code...]\r
- // Output PCM wave [-32768...32767] instead of MAME's voltage level [0...32767]\r
- // - This allows for better s/w mixing\r
-\r
- if (PSG->VolA)\r
- {\r
- if (vola)\r
- *(buf1++) = (vola * PSG->VolA) / STEP;\r
- else\r
- *(buf1++) = -(int)PSG->VolA;\r
- }\r
- else\r
- *(buf1++) = 0;\r
-\r
- if (PSG->VolB)\r
- {\r
- if (volb)\r
- *(buf2++) = (volb * PSG->VolB) / STEP;\r
- else\r
- *(buf2++) = -(int)PSG->VolB;\r
- }\r
- else\r
- *(buf2++) = 0;\r
-\r
- if (PSG->VolC)\r
- {\r
- if (volc)\r
- *(buf3++) = (volc * PSG->VolC) / STEP;\r
- else\r
- *(buf3++) = -(int)PSG->VolC;\r
- }\r
- else\r
- *(buf3++) = 0;\r
-#endif\r
- length--;\r
- }\r
-}\r
-\r
-\r
-static void AY8910_set_clock(int chip, int clock)\r
-{\r
- struct AY8910 * PSG = &AYPSG[chip];\r
-\r
- /* The step clock for the tone and noise generators is the chip clock *\r
- * divided by 8; for the envelope generator of the AY-3-8910, it is half *\r
- * that much (clock/16), but the envelope of the YM2149 goes twice as *\r
- * fast, therefore again clock/8. *\r
- * Here we calculate the number of steps which happen during one sample *\r
- * at the given sample rate. No. of events = sample rate / (clock/8). *\r
- * STEP is a multiplier used to turn the fraction into a fixed point *\r
- * number. */\r
- PSG->UpdateStep = (unsigned int)(((double)STEP * PSG->SampleRate * 8 + clock / 2) / clock); // [TC: unsigned int cast]\r
-}\r
-\r
-\r
-static void build_mixer_table(int chip)\r
-{\r
- struct AY8910 * PSG = &AYPSG[chip];\r
-\r
- /* calculate the volume->voltage conversion table */\r
- /* The AY-3-8910 has 16 levels, in a logarithmic scale (3dB per step) */\r
- /* The YM2149 still has 16 levels for the tone generators, but 32 for */\r
- /* the envelope generator (1.5dB per step). */\r
- double out = MAX_OUTPUT;\r
-\r
- for(int i=31; i>0; i--)\r
- {\r
- PSG->VolTable[i] = (unsigned int)(out + 0.5); /* round to nearest */ // [TC: unsigned int cast]\r
- out /= 1.188502227; /* = 10 ^ (1.5/20) = 1.5dB */\r
- }\r
-\r
- PSG->VolTable[0] = 0;\r
-}\r
-\r
-\r
-void AY8910_reset(int chip)\r
-{\r
- int i;\r
- struct AY8910 * PSG = &AYPSG[chip];\r
-\r
- PSG->register_latch = 0;\r
- PSG->RNG = 1;\r
- PSG->OutputA = 0;\r
- PSG->OutputB = 0;\r
- PSG->OutputC = 0;\r
- PSG->OutputN = 0xFF;\r
- PSG->lastEnable = -1; /* force a write */\r
-\r
- for(i=0; i<AY_PORTA; i++)\r
- _AYWriteReg(chip, i, 0); /* AYWriteReg() uses the timer system; we cannot */\r
- /* call it at this time because the timer system */\r
- /* has not been initialized. */\r
-}\r
-\r
-// This stuff looks like Tom's code, so let's streamline and un-MSHungarianize this shit:\r
-// [DONE]\r
-\r
-void AY8910_InitAll(int clock, int sampleRate)\r
-{\r
- for(int chip=0; chip<MAX_8910; chip++)\r
- {\r
- struct AY8910 * PSG = &AYPSG[chip];\r
-\r
- memset(PSG, 0, sizeof(struct AY8910));\r
- PSG->SampleRate = sampleRate;\r
- AY8910_set_clock(chip, clock);\r
- build_mixer_table(chip);\r
- }\r
-}\r
-\r
-void AY8910_InitClock(int clock)\r
-{\r
- for(int chip=0; chip<MAX_8910; chip++)\r
- AY8910_set_clock(chip, clock);\r
-}\r
-\r
-uint8 * AY8910_GetRegsPtr(uint16 chipNum)\r
-{\r
- if (chipNum >= MAX_8910)\r
- return NULL;\r
-\r
- return &AYPSG[chipNum].Regs[0];\r
-}\r
+/***************************************************************************
+
+ ay8910.cpp
+
+ Emulation of the AY-3-8910 / YM2149 sound chip.
+
+ Based on various code snippets by Ville Hallik, Michael Cuddy,
+ Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria.
+
+***************************************************************************/
+
+//
+// From mame.txt (http://www.mame.net/readme.html)
+//
+// VI. Reuse of Source Code
+// --------------------------
+// This chapter might not apply to specific portions of MAME (e.g. CPU
+// emulators) which bear different copyright notices.
+// The source code cannot be used in a commercial product without the written
+// authorization of the authors. Use in non-commercial products is allowed, and
+// indeed encouraged. If you use portions of the MAME source code in your
+// program, however, you must make the full source code freely available as
+// well.
+// Usage of the _information_ contained in the source code is free for any use.
+// However, given the amount of time and energy it took to collect this
+// information, if you find new information we would appreciate if you made it
+// freely available as well.
+//
+
+// JLH: Commented out MAME specific crap
+
+#include "ay8910.h"
+#include <string.h> // for memset()
+
+#define MAX_OUTPUT 0x7FFF
+
+// See AY8910_set_clock() for definition of STEP
+#define STEP 0x8000
+
+struct AY8910
+{
+ int Channel;
+ int SampleRate;
+// mem_read_handler PortAread;
+// mem_read_handler PortBread;
+// mem_write_handler PortAwrite;
+// mem_write_handler PortBwrite;
+ int register_latch;
+ unsigned char Regs[16];
+ int lastEnable;
+ unsigned int UpdateStep;
+ int PeriodA, PeriodB, PeriodC, PeriodN, PeriodE;
+ int CountA, CountB, CountC, CountN, CountE;
+ unsigned int VolA, VolB, VolC, VolE;
+ unsigned char EnvelopeA, EnvelopeB, EnvelopeC;
+ unsigned char OutputA, OutputB, OutputC, OutputN;
+ signed char CountEnv;
+ unsigned char Hold, Alternate, Attack, Holding;
+ int RNG;
+ unsigned int VolTable[32];
+};
+
+/* register id's */
+#define AY_AFINE (0)
+#define AY_ACOARSE (1)
+#define AY_BFINE (2)
+#define AY_BCOARSE (3)
+#define AY_CFINE (4)
+#define AY_CCOARSE (5)
+#define AY_NOISEPER (6)
+#define AY_ENABLE (7)
+#define AY_AVOL (8)
+#define AY_BVOL (9)
+#define AY_CVOL (10)
+#define AY_EFINE (11)
+#define AY_ECOARSE (12)
+#define AY_ESHAPE (13)
+
+#define AY_PORTA (14)
+#define AY_PORTB (15)
+
+
+static struct AY8910 AYPSG[MAX_8910]; /* array of PSG's */
+
+
+void _AYWriteReg(int n, int r, int v)
+{
+ struct AY8910 *PSG = &AYPSG[n];
+ int old;
+
+ PSG->Regs[r] = v;
+
+ /* A note about the period of tones, noise and envelope: for speed reasons, *
+ * we count down from the period to 0, but careful studies of the chip *
+ * output prove that it instead counts up from 0 until the counter becomes *
+ * greater or equal to the period. This is an important difference when the *
+ * program is rapidly changing the period to modulate the sound. *
+ * To compensate for the difference, when the period is changed we adjust *
+ * our internal counter. *
+ * Also, note that period = 0 is the same as period = 1. This is mentioned *
+ * in the YM2203 data sheets. However, this does NOT apply to the Envelope *
+ * period. In that case, period = 0 is half as period = 1. */
+ switch (r)
+ {
+ case AY_AFINE:
+ case AY_ACOARSE:
+ PSG->Regs[AY_ACOARSE] &= 0x0F;
+ old = PSG->PeriodA;
+ PSG->PeriodA = (PSG->Regs[AY_AFINE] + 256 * PSG->Regs[AY_ACOARSE]) * PSG->UpdateStep;
+
+ if (PSG->PeriodA == 0)
+ PSG->PeriodA = PSG->UpdateStep;
+
+ PSG->CountA += PSG->PeriodA - old;
+
+ if (PSG->CountA <= 0)
+ PSG->CountA = 1;
+ break;
+ case AY_BFINE:
+ case AY_BCOARSE:
+ PSG->Regs[AY_BCOARSE] &= 0x0F;
+ old = PSG->PeriodB;
+ PSG->PeriodB = (PSG->Regs[AY_BFINE] + 256 * PSG->Regs[AY_BCOARSE]) * PSG->UpdateStep;
+
+ if (PSG->PeriodB == 0)
+ PSG->PeriodB = PSG->UpdateStep;
+
+ PSG->CountB += PSG->PeriodB - old;
+
+ if (PSG->CountB <= 0)
+ PSG->CountB = 1;
+ break;
+ case AY_CFINE:
+ case AY_CCOARSE:
+ PSG->Regs[AY_CCOARSE] &= 0x0F;
+ old = PSG->PeriodC;
+ PSG->PeriodC = (PSG->Regs[AY_CFINE] + 256 * PSG->Regs[AY_CCOARSE]) * PSG->UpdateStep;
+
+ if (PSG->PeriodC == 0)
+ PSG->PeriodC = PSG->UpdateStep;
+
+ PSG->CountC += PSG->PeriodC - old;
+
+ if (PSG->CountC <= 0)
+ PSG->CountC = 1;
+ break;
+ case AY_NOISEPER:
+ PSG->Regs[AY_NOISEPER] &= 0x1F;
+ old = PSG->PeriodN;
+ PSG->PeriodN = PSG->Regs[AY_NOISEPER] * PSG->UpdateStep;
+
+ if (PSG->PeriodN == 0)
+ PSG->PeriodN = PSG->UpdateStep;
+
+ PSG->CountN += PSG->PeriodN - old;
+
+ if (PSG->CountN <= 0)
+ PSG->CountN = 1;
+ break;
+ case AY_ENABLE:
+ if ((PSG->lastEnable == -1) ||
+ ((PSG->lastEnable & 0x40) != (PSG->Regs[AY_ENABLE] & 0x40)))
+ {
+ /* write out 0xff if port set to input */
+// if (PSG->PortAwrite)
+// (*PSG->PortAwrite)(0, (UINT8) ((PSG->Regs[AY_ENABLE] & 0x40) ? PSG->Regs[AY_PORTA] : 0xff)); // [TC: UINT8 cast]
+ }
+
+ if ((PSG->lastEnable == -1) ||
+ ((PSG->lastEnable & 0x80) != (PSG->Regs[AY_ENABLE] & 0x80)))
+ {
+ /* write out 0xff if port set to input */
+// if (PSG->PortBwrite)
+// (*PSG->PortBwrite)(0, (UINT8) ((PSG->Regs[AY_ENABLE] & 0x80) ? PSG->Regs[AY_PORTB] : 0xff)); // [TC: UINT8 cast]
+ }
+
+ PSG->lastEnable = PSG->Regs[AY_ENABLE];
+ break;
+ case AY_AVOL:
+ PSG->Regs[AY_AVOL] &= 0x1F;
+ PSG->EnvelopeA = PSG->Regs[AY_AVOL] & 0x10;
+ PSG->VolA = PSG->EnvelopeA ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_AVOL] ? PSG->Regs[AY_AVOL]*2+1 : 0];
+ break;
+ case AY_BVOL:
+ PSG->Regs[AY_BVOL] &= 0x1F;
+ PSG->EnvelopeB = PSG->Regs[AY_BVOL] & 0x10;
+ PSG->VolB = PSG->EnvelopeB ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_BVOL] ? PSG->Regs[AY_BVOL]*2+1 : 0];
+ break;
+ case AY_CVOL:
+ PSG->Regs[AY_CVOL] &= 0x1F;
+ PSG->EnvelopeC = PSG->Regs[AY_CVOL] & 0x10;
+ PSG->VolC = PSG->EnvelopeC ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_CVOL] ? PSG->Regs[AY_CVOL]*2+1 : 0];
+ break;
+ case AY_EFINE:
+ case AY_ECOARSE:
+ old = PSG->PeriodE;
+ PSG->PeriodE = ((PSG->Regs[AY_EFINE] + 256 * PSG->Regs[AY_ECOARSE])) * PSG->UpdateStep;
+
+ if (PSG->PeriodE == 0)
+ PSG->PeriodE = PSG->UpdateStep / 2;
+
+ PSG->CountE += PSG->PeriodE - old;
+
+ if (PSG->CountE <= 0)
+ PSG->CountE = 1;
+ break;
+ case AY_ESHAPE:
+ /* envelope shapes:
+ C AtAlH
+ 0 0 x x \___
+
+ 0 1 x x /___
+
+ 1 0 0 0 \\\\
+
+ 1 0 0 1 \___
+
+ 1 0 1 0 \/\/
+ ___
+ 1 0 1 1 \
+
+ 1 1 0 0 ////
+ ___
+ 1 1 0 1 /
+
+ 1 1 1 0 /\/\
+
+ 1 1 1 1 /___
+
+ The envelope counter on the AY-3-8910 has 16 steps. On the YM2149 it
+ has twice the steps, happening twice as fast. Since the end result is
+ just a smoother curve, we always use the YM2149 behaviour.
+ */
+ PSG->Regs[AY_ESHAPE] &= 0x0F;
+ PSG->Attack = (PSG->Regs[AY_ESHAPE] & 0x04) ? 0x1F : 0x00;
+
+ if ((PSG->Regs[AY_ESHAPE] & 0x08) == 0)
+ {
+ /* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */
+ PSG->Hold = 1;
+ PSG->Alternate = PSG->Attack;
+ }
+ else
+ {
+ PSG->Hold = PSG->Regs[AY_ESHAPE] & 0x01;
+ PSG->Alternate = PSG->Regs[AY_ESHAPE] & 0x02;
+ }
+
+ PSG->CountE = PSG->PeriodE;
+ PSG->CountEnv = 0x1F;
+ PSG->Holding = 0;
+ PSG->VolE = PSG->VolTable[PSG->CountEnv ^ PSG->Attack];
+
+ if (PSG->EnvelopeA)
+ PSG->VolA = PSG->VolE;
+
+ if (PSG->EnvelopeB)
+ PSG->VolB = PSG->VolE;
+
+ if (PSG->EnvelopeC)
+ PSG->VolC = PSG->VolE;
+ break;
+ case AY_PORTA:
+ if (PSG->Regs[AY_ENABLE] & 0x40)
+ {
+// if (PSG->PortAwrite)
+// (*PSG->PortAwrite)(0, PSG->Regs[AY_PORTA]);
+// else
+// logerror("PC %04x: warning - write %02x to 8910 #%d Port A\n",activecpu_get_pc(),PSG->Regs[AY_PORTA],n);
+ }
+ else
+ {
+// logerror("warning: write to 8910 #%d Port A set as input - ignored\n",n);
+ }
+ break;
+ case AY_PORTB:
+ if (PSG->Regs[AY_ENABLE] & 0x80)
+ {
+// if (PSG->PortBwrite)
+// (*PSG->PortBwrite)(0, PSG->Regs[AY_PORTB]);
+// else
+// logerror("PC %04x: warning - write %02x to 8910 #%d Port B\n",activecpu_get_pc(),PSG->Regs[AY_PORTB],n);
+ }
+ else
+ {
+// logerror("warning: write to 8910 #%d Port B set as input - ignored\n",n);
+ }
+ break;
+ }
+}
+
+
+// /length/ is the number of samples we require
+// NB. This should be called at twice the 6522 IRQ rate or (eg) 60Hz if no IRQ.
+void AY8910Update(int chip, int16 ** buffer, int length) // [TC: Removed static]
+{
+ struct AY8910 * PSG = &AYPSG[chip];
+ int16 * buf1, * buf2, * buf3;
+ int outn;
+
+ buf1 = buffer[0];
+ buf2 = buffer[1];
+ buf3 = buffer[2];
+
+ /* The 8910 has three outputs, each output is the mix of one of the three *
+ * tone generators and of the (single) noise generator. The two are mixed *
+ * BEFORE going into the DAC. The formula to mix each channel is: *
+ * (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). *
+ * Note that this means that if both tone and noise are disabled, the output *
+ * is 1, not 0, and can be modulated changing the volume. *
+ * *
+ * If the channels are disabled, set their output to 1, and increase the *
+ * counter, if necessary, so they will not be inverted during this update. *
+ * Setting the output to 1 is necessary because a disabled channel is locked *
+ * into the ON state (see above); and it has no effect if the volume is 0. *
+ * If the volume is 0, increase the counter, but don't touch the output. */
+ if (PSG->Regs[AY_ENABLE] & 0x01)
+ {
+ if (PSG->CountA <= length * STEP)
+ PSG->CountA += length * STEP;
+
+ PSG->OutputA = 1;
+ }
+ else if (PSG->Regs[AY_AVOL] == 0)
+ {
+ /* note that I do count += length, NOT count = length + 1. You might think *
+ * it's the same since the volume is 0, but doing the latter could cause *
+ * interferencies when the program is rapidly modulating the volume. */
+ if (PSG->CountA <= length * STEP)
+ PSG->CountA += length * STEP;
+ }
+
+ if (PSG->Regs[AY_ENABLE] & 0x02)
+ {
+ if (PSG->CountB <= length * STEP)
+ PSG->CountB += length * STEP;
+
+ PSG->OutputB = 1;
+ }
+ else if (PSG->Regs[AY_BVOL] == 0)
+ {
+ if (PSG->CountB <= length * STEP)
+ PSG->CountB += length * STEP;
+ }
+
+ if (PSG->Regs[AY_ENABLE] & 0x04)
+ {
+ if (PSG->CountC <= length * STEP)
+ PSG->CountC += length * STEP;
+
+ PSG->OutputC = 1;
+ }
+ else if (PSG->Regs[AY_CVOL] == 0)
+ {
+ if (PSG->CountC <= length * STEP)
+ PSG->CountC += length * STEP;
+ }
+
+ /* for the noise channel we must not touch OutputN - it's also not necessary *
+ * since we use outn. */
+ if ((PSG->Regs[AY_ENABLE] & 0x38) == 0x38) /* all off */
+ if (PSG->CountN <= length * STEP)
+ PSG->CountN += length * STEP;
+
+ outn = (PSG->OutputN | PSG->Regs[AY_ENABLE]);
+
+ /* buffering loop */
+ while (length)
+ {
+ int vola, volb, volc;
+ int left;
+
+ /* vola, volb and volc keep track of how long each square wave stays *
+ * in the 1 position during the sample period. */
+ vola = volb = volc = 0;
+
+ left = STEP;
+ do
+ {
+ int nextevent;
+
+ if (PSG->CountN < left)
+ nextevent = PSG->CountN;
+ else
+ nextevent = left;
+
+ if (outn & 0x08)
+ {
+ if (PSG->OutputA)
+ vola += PSG->CountA;
+
+ PSG->CountA -= nextevent;
+ /* PeriodA is the half period of the square wave. Here, in each *
+ * loop I add PeriodA twice, so that at the end of the loop the *
+ * square wave is in the same status (0 or 1) it was at the start. *
+ * vola is also incremented by PeriodA, since the wave has been 1 *
+ * exactly half of the time, regardless of the initial position. *
+ * If we exit the loop in the middle, OutputA has to be inverted *
+ * and vola incremented only if the exit status of the square *
+ * wave is 1. */
+ while (PSG->CountA <= 0)
+ {
+ PSG->CountA += PSG->PeriodA;
+
+ if (PSG->CountA > 0)
+ {
+ PSG->OutputA ^= 1;
+
+ if (PSG->OutputA)
+ vola += PSG->PeriodA;
+ break;
+ }
+
+ PSG->CountA += PSG->PeriodA;
+ vola += PSG->PeriodA;
+ }
+
+ if (PSG->OutputA)
+ vola -= PSG->CountA;
+ }
+ else
+ {
+ PSG->CountA -= nextevent;
+ while (PSG->CountA <= 0)
+ {
+ PSG->CountA += PSG->PeriodA;
+
+ if (PSG->CountA > 0)
+ {
+ PSG->OutputA ^= 1;
+ break;
+ }
+
+ PSG->CountA += PSG->PeriodA;
+ }
+ }
+
+ if (outn & 0x10)
+ {
+ if (PSG->OutputB)
+ volb += PSG->CountB;
+
+ PSG->CountB -= nextevent;
+
+ while (PSG->CountB <= 0)
+ {
+ PSG->CountB += PSG->PeriodB;
+
+ if (PSG->CountB > 0)
+ {
+ PSG->OutputB ^= 1;
+
+ if (PSG->OutputB)
+ volb += PSG->PeriodB;
+ break;
+ }
+
+ PSG->CountB += PSG->PeriodB;
+ volb += PSG->PeriodB;
+ }
+
+ if (PSG->OutputB)
+ volb -= PSG->CountB;
+ }
+ else
+ {
+ PSG->CountB -= nextevent;
+
+ while (PSG->CountB <= 0)
+ {
+ PSG->CountB += PSG->PeriodB;
+
+ if (PSG->CountB > 0)
+ {
+ PSG->OutputB ^= 1;
+ break;
+ }
+
+ PSG->CountB += PSG->PeriodB;
+ }
+ }
+
+ if (outn & 0x20)
+ {
+ if (PSG->OutputC)
+ volc += PSG->CountC;
+
+ PSG->CountC -= nextevent;
+
+ while (PSG->CountC <= 0)
+ {
+ PSG->CountC += PSG->PeriodC;
+
+ if (PSG->CountC > 0)
+ {
+ PSG->OutputC ^= 1;
+
+ if (PSG->OutputC)
+ volc += PSG->PeriodC;
+ break;
+ }
+
+ PSG->CountC += PSG->PeriodC;
+ volc += PSG->PeriodC;
+ }
+
+ if (PSG->OutputC)
+ volc -= PSG->CountC;
+ }
+ else
+ {
+ PSG->CountC -= nextevent;
+
+ while (PSG->CountC <= 0)
+ {
+ PSG->CountC += PSG->PeriodC;
+
+ if (PSG->CountC > 0)
+ {
+ PSG->OutputC ^= 1;
+ break;
+ }
+
+ PSG->CountC += PSG->PeriodC;
+ }
+ }
+
+ PSG->CountN -= nextevent;
+
+ if (PSG->CountN <= 0)
+ {
+ /* Is noise output going to change? */
+ if ((PSG->RNG + 1) & 0x00002) /* (bit0^bit1)? */
+ {
+ PSG->OutputN = ~PSG->OutputN;
+ outn = (PSG->OutputN | PSG->Regs[AY_ENABLE]);
+ }
+
+ /* The Random Number Generator of the 8910 is a 17-bit shift *
+ * register. The input to the shift register is bit0 XOR bit3 *
+ * (bit0 is the output). This was verified on AY-3-8910 and *
+ * YM2149 chips. *
+ * *
+ * The following is a fast way to compute bit17 = bit0^bit3. *
+ * Instead of doing all the logic operations, we only check *
+ * bit0, relying on the fact that after three shifts of the *
+ * register, what now is bit3 will become bit0, and will *
+ * invert, if necessary, bit14, which previously was bit17. */
+ if (PSG->RNG & 0x00001)
+ PSG->RNG ^= 0x24000; /* This version is called the "Galois configuration". */
+
+ PSG->RNG >>= 1;
+ PSG->CountN += PSG->PeriodN;
+ }
+
+ left -= nextevent;
+ }
+ while (left > 0);
+
+ /* update envelope */
+ if (PSG->Holding == 0)
+ {
+ PSG->CountE -= STEP;
+
+ if (PSG->CountE <= 0)
+ {
+ do
+ {
+ PSG->CountEnv--;
+ PSG->CountE += PSG->PeriodE;
+ }
+ while (PSG->CountE <= 0);
+
+ /* check envelope current position */
+ if (PSG->CountEnv < 0)
+ {
+ if (PSG->Hold)
+ {
+ if (PSG->Alternate)
+ PSG->Attack ^= 0x1F;
+
+ PSG->Holding = 1;
+ PSG->CountEnv = 0;
+ }
+ else
+ {
+ /* if CountEnv has looped an odd number of times (usually 1), *
+ * invert the output. */
+ if (PSG->Alternate && (PSG->CountEnv & 0x20))
+ PSG->Attack ^= 0x1F;
+
+ PSG->CountEnv &= 0x1F;
+ }
+ }
+
+ PSG->VolE = PSG->VolTable[PSG->CountEnv ^ PSG->Attack];
+ /* reload volume */
+ if (PSG->EnvelopeA)
+ PSG->VolA = PSG->VolE;
+
+ if (PSG->EnvelopeB)
+ PSG->VolB = PSG->VolE;
+
+ if (PSG->EnvelopeC)
+ PSG->VolC = PSG->VolE;
+ }
+ }
+
+#if 0
+ *(buf1++) = (vola * PSG->VolA) / STEP;
+ *(buf2++) = (volb * PSG->VolB) / STEP;
+ *(buf3++) = (volc * PSG->VolC) / STEP;
+#else // [Tom's code...]
+ // Output PCM wave [-32768...32767] instead of MAME's voltage level [0...32767]
+ // - This allows for better s/w mixing
+
+ if (PSG->VolA)
+ {
+ if (vola)
+ *(buf1++) = (vola * PSG->VolA) / STEP;
+ else
+ *(buf1++) = -(int)PSG->VolA;
+ }
+ else
+ *(buf1++) = 0;
+
+ if (PSG->VolB)
+ {
+ if (volb)
+ *(buf2++) = (volb * PSG->VolB) / STEP;
+ else
+ *(buf2++) = -(int)PSG->VolB;
+ }
+ else
+ *(buf2++) = 0;
+
+ if (PSG->VolC)
+ {
+ if (volc)
+ *(buf3++) = (volc * PSG->VolC) / STEP;
+ else
+ *(buf3++) = -(int)PSG->VolC;
+ }
+ else
+ *(buf3++) = 0;
+#endif
+ length--;
+ }
+}
+
+
+static void AY8910_set_clock(int chip, int clock)
+{
+ struct AY8910 * PSG = &AYPSG[chip];
+
+ /* The step clock for the tone and noise generators is the chip clock *
+ * divided by 8; for the envelope generator of the AY-3-8910, it is half *
+ * that much (clock/16), but the envelope of the YM2149 goes twice as *
+ * fast, therefore again clock/8. *
+ * Here we calculate the number of steps which happen during one sample *
+ * at the given sample rate. No. of events = sample rate / (clock/8). *
+ * STEP is a multiplier used to turn the fraction into a fixed point *
+ * number. */
+ PSG->UpdateStep = (unsigned int)(((double)STEP * PSG->SampleRate * 8 + clock / 2) / clock); // [TC: unsigned int cast]
+}
+
+
+static void build_mixer_table(int chip)
+{
+ struct AY8910 * PSG = &AYPSG[chip];
+
+ /* calculate the volume->voltage conversion table */
+ /* The AY-3-8910 has 16 levels, in a logarithmic scale (3dB per step) */
+ /* The YM2149 still has 16 levels for the tone generators, but 32 for */
+ /* the envelope generator (1.5dB per step). */
+ double out = MAX_OUTPUT;
+
+ for(int i=31; i>0; i--)
+ {
+ PSG->VolTable[i] = (unsigned int)(out + 0.5); /* round to nearest */ // [TC: unsigned int cast]
+ out /= 1.188502227; /* = 10 ^ (1.5/20) = 1.5dB */
+ }
+
+ PSG->VolTable[0] = 0;
+}
+
+
+void AY8910_reset(int chip)
+{
+ int i;
+ struct AY8910 * PSG = &AYPSG[chip];
+
+ PSG->register_latch = 0;
+ PSG->RNG = 1;
+ PSG->OutputA = 0;
+ PSG->OutputB = 0;
+ PSG->OutputC = 0;
+ PSG->OutputN = 0xFF;
+ PSG->lastEnable = -1; /* force a write */
+
+ for(i=0; i<AY_PORTA; i++)
+ _AYWriteReg(chip, i, 0); /* AYWriteReg() uses the timer system; we cannot */
+ /* call it at this time because the timer system */
+ /* has not been initialized. */
+}
+
+// This stuff looks like Tom's code, so let's streamline and un-MSHungarianize this shit:
+// [DONE]
+
+void AY8910_InitAll(int clock, int sampleRate)
+{
+ for(int chip=0; chip<MAX_8910; chip++)
+ {
+ struct AY8910 * PSG = &AYPSG[chip];
+
+ memset(PSG, 0, sizeof(struct AY8910));
+ PSG->SampleRate = sampleRate;
+ AY8910_set_clock(chip, clock);
+ build_mixer_table(chip);
+ }
+}
+
+void AY8910_InitClock(int clock)
+{
+ for(int chip=0; chip<MAX_8910; chip++)
+ AY8910_set_clock(chip, clock);
+}
+
+uint8 * AY8910_GetRegsPtr(uint16 chipNum)
+{
+ if (chipNum >= MAX_8910)
+ return NULL;
+
+ return &AYPSG[chipNum].Regs[0];
+}