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