1 /** EMULib Emulation Library *********************************/
2 /**                                                         **/
3 /**                         AY8910.c                        **/
4 /**                                                         **/
5 /** This file contains emulation for the AY8910 sound chip  **/
6 /** produced by General Instruments, Yamaha, etc. See       **/
7 /** AY8910.h for declarations.                              **/
8 /**                                                         **/
9 /** Copyright (C) Marat Fayzullin 1996-2014                 **/
10 /**     You are not allowed to distribute this software     **/
11 /**     commercially. Please, notify me, if you make any    **/
12 /**     changes to this file.                               **/
13 /*************************************************************/
14 
15 #include "AY8910.h"
16 #include "Sound.h"
17 #include <string.h>
18 
19 static const unsigned char Envelopes[16][32] =
20 {
21   { 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 },
22   { 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 },
23   { 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 },
24   { 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 },
25   { 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 },
26   { 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 },
27   { 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 },
28   { 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 },
29   { 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 },
30   { 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 },
31   { 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 },
32   { 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 },
33   { 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 },
34   { 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 },
35   { 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 },
36   { 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 }
37 };
38 
39 static const int Volumes[16] =
40 { 0,1,2,4,6,8,11,16,23,32,45,64,90,128,180,255 };
41 //{ 0,16,33,50,67,84,101,118,135,152,169,186,203,220,237,254 };
42 
43 /** Reset8910() **********************************************/
44 /** Reset the sound chip and use sound channels from the    **/
45 /** one given in First.                                     **/
46 /*************************************************************/
Reset8910(register AY8910 * D,int ClockHz,int First)47 void Reset8910(register AY8910 *D,int ClockHz,int First)
48 {
49   static byte RegInit[16] =
50   {
51     0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFD,
52     0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00
53   };
54   int J;
55 
56   /* Reset state */
57   memcpy(D->R,RegInit,sizeof(D->R));
58   D->EPhase  = 0;
59   D->Clock   = ClockHz>>4;
60   D->First   = First;
61   D->Sync    = AY8910_ASYNC;
62   D->Changed = 0x00;
63   D->EPeriod = 0;
64   D->ECount  = 0;
65   D->Latch   = 0x00;
66 
67   /* Set sound types */
68   SetSound(0+First,SND_MELODIC);
69   SetSound(1+First,SND_MELODIC);
70   SetSound(2+First,SND_MELODIC);
71   SetSound(3+First,SND_NOISE);
72   SetSound(4+First,SND_NOISE);
73   SetSound(5+First,SND_NOISE);
74 
75   /* Silence all channels */
76   for(J=0;J<AY8910_CHANNELS;J++)
77   {
78     D->Freq[J]=D->Volume[J]=0;
79     Sound(J+First,0,0);
80   }
81 }
82 
83 /** WrCtrl8910() *********************************************/
84 /** Write a value V to the PSG Control Port.                **/
85 /*************************************************************/
WrCtrl8910(AY8910 * D,byte V)86 void WrCtrl8910(AY8910 *D,byte V)
87 {
88   D->Latch=V&0x0F;
89 }
90 
91 /** WrData8910() *********************************************/
92 /** Write a value V to the PSG Data Port.                   **/
93 /*************************************************************/
WrData8910(AY8910 * D,byte V)94 void WrData8910(AY8910 *D,byte V)
95 {
96   Write8910(D,D->Latch,V);
97 }
98 
99 /** RdData8910() *********************************************/
100 /** Read a value from the PSG Data Port.                    **/
101 /*************************************************************/
RdData8910(AY8910 * D)102 byte RdData8910(AY8910 *D)
103 {
104   return(D->R[D->Latch]);
105 }
106 
107 /** Write8910() **********************************************/
108 /** Call this function to output a value V into the sound   **/
109 /** chip.                                                   **/
110 /*************************************************************/
Write8910(register AY8910 * D,register byte R,register byte V)111 void Write8910(register AY8910 *D,register byte R,register byte V)
112 {
113   register int J,I;
114 
115   switch(R)
116   {
117     case 1:
118     case 3:
119     case 5:
120       V&=0x0F;
121       /* Fall through */
122     case 0:
123     case 2:
124     case 4:
125       /* Write value */
126       D->R[R]=V;
127       /* Exit if the channel is silenced */
128       if(D->R[7]&(1<<(R>>1))) return;
129       /* Go to the first register in the pair */
130       R&=0xFE;
131       /* Compute frequency */
132       J=((int)(D->R[R+1]&0x0F)<<8)+D->R[R];
133       /* Compute channel number */
134       R>>=1;
135       /* Assign frequency */
136       D->Freq[R]=D->Clock/(J? J:0x1000);
137       /* Compute changed channels mask */
138       D->Changed|=1<<R;
139       break;
140 
141     case 6:
142       /* Write value */
143       D->R[6]=V&=0x1F;
144       /* Exit if noise channels are silenced */
145       if(!(~D->R[7]&0x38)) return;
146       /* Compute and assign noise frequency */
147       /* Shouldn't do <<2, but need to keep frequency down */
148       J=D->Clock/((V&0x1F? (V&0x1F):0x20)<<2);
149       if(!(D->R[7]&0x08)) D->Freq[3]=J;
150       if(!(D->R[7]&0x10)) D->Freq[4]=J;
151       if(!(D->R[7]&0x20)) D->Freq[5]=J;
152       /* Compute changed channels mask */
153       D->Changed|=0x38&~D->R[7];
154       break;
155 
156     case 7:
157       /* Find changed channels */
158       R=(V^D->R[7])&0x3F;
159       D->Changed|=R;
160       /* Write value */
161       D->R[7]=V;
162       /* Update frequencies */
163       for(J=0;R&&(J<AY8910_CHANNELS);J++,R>>=1,V>>=1)
164         if(R&1)
165         {
166           if(V&1) D->Freq[J]=0;
167           else if(J<3)
168           {
169             I=((int)(D->R[J*2+1]&0x0F)<<8)+D->R[J*2];
170             D->Freq[J]=D->Clock/(I? I:0x1000);
171           }
172           else
173           {
174             /* Shouldn't do <<2, but need to keep frequency down */
175             I=D->R[6]&0x1F;
176             D->Freq[J]=D->Clock/((I? I:0x20)<<2);
177           }
178         }
179       break;
180 
181     case 8:
182     case 9:
183     case 10:
184       /* Write value */
185       D->R[R]=V&=0x1F;
186       /* Compute channel number */
187       R-=8;
188       /* Compute and assign new volume */
189       J=Volumes[V&0x10? Envelopes[D->R[13]&0x0F][D->EPhase]:(V&0x0F)];
190       D->Volume[R]=J;
191       D->Volume[R+3]=(J+1)>>1;
192       /* Compute changed channels mask */
193       D->Changed|=(0x09<<R)&~D->R[7];
194       break;
195 
196     case 11:
197     case 12:
198       /* Write value */
199       D->R[R]=V;
200       /* Compute envelope period (why not <<4?) */
201       J=((int)D->R[12]<<8)+D->R[11];
202       D->EPeriod=1000*(J? J:0x10000)/D->Clock;
203       /* No channels changed */
204       return;
205 
206     case 13:
207       /* Write value */
208       D->R[R]=V&=0x0F;
209       /* Reset envelope */
210       D->ECount = 0;
211       D->EPhase = 0;
212       for(J=0;J<AY8910_CHANNELS/2;++J)
213         if(D->R[J+8]&0x10)
214         {
215           I = Volumes[Envelopes[V][0]];
216           D->Volume[J]   = I;
217           D->Volume[J+3] = (I+1)>>1;
218           D->Changed    |= (0x09<<J)&~D->R[7];
219         }
220       break;
221 
222     case 14:
223     case 15:
224       /* Write value */
225       D->R[R]=V;
226       /* No channels changed */
227       return;
228 
229     default:
230       /* Wrong register, do nothing */
231       return;
232   }
233 
234   /* For asynchronous mode, make Sound() calls right away */
235   if(!D->Sync&&D->Changed) Sync8910(D,AY8910_FLUSH);
236 }
237 
238 /** Loop8910() ***********************************************/
239 /** Call this function periodically to update volume        **/
240 /** envelopes. Use mS to pass the time since the last call  **/
241 /** of Loop8910() in milliseconds.                          **/
242 /*************************************************************/
Loop8910(register AY8910 * D,int mS)243 void Loop8910(register AY8910 *D,int mS)
244 {
245   register int J,I;
246 
247   /* Exit if no envelope running */
248   if(!D->EPeriod) return;
249 
250   /* Count milliseconds */
251   D->ECount += mS;
252   if(D->ECount<D->EPeriod) return;
253 
254   /* Count steps */
255   J = D->ECount/D->EPeriod;
256   D->ECount -= J*D->EPeriod;
257 
258   /* Count phases */
259   D->EPhase += J;
260   if(D->EPhase>31)
261     D->EPhase = (D->R[13]&0x09)==0x08? (D->EPhase&0x1F):31;
262 
263   /* Set envelope volumes for relevant channels */
264   for(I=0;I<3;++I)
265     if(D->R[I+8]&0x10)
266     {
267       J = Volumes[Envelopes[D->R[13]&0x0F][D->EPhase]];
268       D->Volume[I]   = J;
269       D->Volume[I+3] = (J+1)>>1;
270       D->Changed    |= (0x09<<I)&~D->R[7];
271     }
272 
273   /* For asynchronous mode, make Sound() calls right away */
274   if(!D->Sync&&D->Changed) Sync8910(D,AY8910_FLUSH);
275 }
276 
277 /** Sync8910() ***********************************************/
278 /** Flush all accumulated changes by issuing Sound() calls  **/
279 /** and set the synchronization on/off. The second argument **/
280 /** should be AY8910_SYNC/AY8910_ASYNC to set/reset sync,   **/
281 /** or AY8910_FLUSH to leave sync mode as it is. To emulate **/
282 /** noise channels with MIDI drums, OR second argument with **/
283 /** AY8910_DRUMS.                                           **/
284 /*************************************************************/
Sync8910(register AY8910 * D,register byte Sync)285 void Sync8910(register AY8910 *D,register byte Sync)
286 {
287   register int J,I;
288 
289   /* Hit MIDI drums for noise channels, if requested */
290   if(Sync&AY8910_DRUMS)
291   {
292     Sync&=~AY8910_DRUMS;
293     J = (D->Freq[3]? D->Volume[3]:0)
294       + (D->Freq[4]? D->Volume[4]:0)
295       + (D->Freq[5]? D->Volume[5]:0);
296     if(J) Drum(DRM_MIDI|28,(J+2)/3);
297   }
298 
299   if(Sync!=AY8910_FLUSH) D->Sync=Sync;
300 
301   for(J=0,I=D->Changed;I&&(J<AY8910_CHANNELS);J++,I>>=1)
302     if(I&1) Sound(J+D->First,D->Freq[J],D->Volume[J]);
303 
304   D->Changed=0x00;
305 }
306