📄 ay8910.c
字号:
/*************************************************************************** ay8910.c 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.***************************************************************************/#include "driver.h"#include <stdio.h>#include "ay8910.h"#include "streams.h"#include "sasound.h"#define MAX_OUTPUT 0x7fff#define STEP 0x8000struct 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]; 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_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) == 0) //logerror("warning: write to 8910 #%d Port A set as input\n",n);if (PSG->PortAwrite) (*PSG->PortAwrite)(0,v); // else logerror("PC %04x: warning - write %02x to 8910 #%d Port A\n",cpu_get_pc(),v,n); break; case AY_PORTB: if ((PSG->Regs[AY_ENABLE] & 0x80) == 0) //logerror("warning: write to 8910 #%d Port B set as input\n",n);if (PSG->PortBwrite) (*PSG->PortBwrite)(0,v); //else logerror("PC %04x: warning - write %02x to 8910 #%d Port B\n",cpu_get_pc(),v,n); break; }}/* write a register on AY8910 chip number 'n' */void AYWriteReg(int chip, int r, int v){ struct AY8910 *PSG = &AYPSG[chip]; if (r > 15) return; if (r < 14) { if (r == AY_ESHAPE || PSG->Regs[r] != v) { /* update the output buffer before changing the register */ stream_update(PSG->Channel,0); } } _AYWriteReg(chip,r,v);}unsigned char AYReadReg(int n, int r){ struct AY8910 *PSG = &AYPSG[n]; if (r > 15) return 0; switch (r) { case AY_PORTA: if ((PSG->Regs[AY_ENABLE] & 0x40) != 0) //logerror("warning: read from 8910 #%d Port A set as output\n",n);if (PSG->PortAread) PSG->Regs[AY_PORTA] = (*PSG->PortAread)(0); //else logerror("PC %04x: warning - read 8910 #%d Port A\n",cpu_get_pc(),n); break; case AY_PORTB: if ((PSG->Regs[AY_ENABLE] & 0x80) != 0) //logerror("warning: read from 8910 #%d Port B set as output\n",n);if (PSG->PortBread) PSG->Regs[AY_PORTB] = (*PSG->PortBread)(0); //else logerror("PC %04x: warning - read 8910 #%d Port B\n",cpu_get_pc(),n); break; } return PSG->Regs[r];}void AY8910Write(int chip,int a,int data){ struct AY8910 *PSG = &AYPSG[chip]; if (a & 1) { /* Data port */ AYWriteReg(chip,PSG->register_latch,data); } else { /* Register port */ PSG->register_latch = data & 0x0f; }}int AY8910Read(int chip){ struct AY8910 *PSG = &AYPSG[chip]; return AYReadReg(chip,PSG->register_latch);}/* AY8910 interface */READ_HANDLER( AY8910_read_port_0_r ) { return AY8910Read(0); }READ_HANDLER( AY8910_read_port_1_r ) { return AY8910Read(1); }READ_HANDLER( AY8910_read_port_2_r ) { return AY8910Read(2); }READ_HANDLER( AY8910_read_port_3_r ) { return AY8910Read(3); }READ_HANDLER( AY8910_read_port_4_r ) { return AY8910Read(4); }WRITE_HANDLER( AY8910_control_port_0_w ) { AY8910Write(0,0,data); }WRITE_HANDLER( AY8910_control_port_1_w ) { AY8910Write(1,0,data); }WRITE_HANDLER( AY8910_control_port_2_w ) { AY8910Write(2,0,data); }WRITE_HANDLER( AY8910_control_port_3_w ) { AY8910Write(3,0,data); }WRITE_HANDLER( AY8910_control_port_4_w ) { AY8910Write(4,0,data); }WRITE_HANDLER( AY8910_write_port_0_w ) { AY8910Write(0,1,data); }WRITE_HANDLER( AY8910_write_port_1_w ) { AY8910Write(1,1,data); }WRITE_HANDLER( AY8910_write_port_2_w ) { AY8910Write(2,1,data); }WRITE_HANDLER( AY8910_write_port_3_w ) { AY8910Write(3,1,data); }WRITE_HANDLER( AY8910_write_port_4_w ) { AY8910Write(4,1,data); }static void AY8910Update(int chip,INT16 **buffer,int length){ 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)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -