sn76496: simplify writes
[picodrive.git] / pico / sound / sn76496.c
CommitLineData
cc68a136 1/***************************************************************************\r
2\r
3 sn76496.c\r
4\r
5 Routines to emulate the Texas Instruments SN76489 / SN76496 programmable\r
6 tone /noise generator. Also known as (or at least compatible with) TMS9919.\r
7\r
8 Noise emulation is not accurate due to lack of documentation. The noise\r
9 generator uses a shift register with a XOR-feedback network, but the exact\r
10 layout is unknown. It can be set for either period or white noise; again,\r
11 the details are unknown.\r
12\r
13 28/03/2005 : Sebastien Chevalier\r
14 Update th SN76496Write func, according to SN76489 doc found on SMSPower.\r
15 - On write with 0x80 set to 0, when LastRegister is other then TONE,\r
16 the function is similar than update with 0x80 set to 1\r
17***************************************************************************/\r
18\r
19#ifndef __GNUC__\r
20#pragma warning (disable:4244)\r
21#endif\r
22\r
23#include "sn76496.h"\r
24\r
25#define MAX_OUTPUT 0x47ff // was 0x7fff\r
26\r
27#define STEP 0x10000\r
28\r
29\r
30/* Formulas for noise generator */\r
31/* bit0 = output */\r
32\r
33/* noise feedback for white noise mode (verified on real SN76489 by John Kortink) */\r
34#define FB_WNOISE 0x14002 /* (16bits) bit16 = bit0(out) ^ bit2 ^ bit15 */\r
35\r
36/* noise feedback for periodic noise mode */\r
37//#define FB_PNOISE 0x10000 /* 16bit rorate */\r
38#define FB_PNOISE 0x08000 /* JH 981127 - fixes Do Run Run */\r
39\r
40/*\r
410x08000 is definitely wrong. The Master System conversion of Marble Madness\r
42uses periodic noise as a baseline. With a 15-bit rotate, the bassline is\r
43out of tune.\r
44The 16-bit rotate has been confirmed against a real PAL Sega Master System 2.\r
45Hope that helps the System E stuff, more news on the PSG as and when!\r
46*/\r
47\r
48/* noise generator start preset (for periodic noise) */\r
49#define NG_PRESET 0x0f35\r
50\r
51\r
52struct SN76496\r
53{\r
54 //sound_stream * Channel;\r
55 int SampleRate;\r
56 unsigned int UpdateStep;\r
57 int VolTable[16]; /* volume table */\r
58 int Register[8]; /* registers */\r
59 int LastRegister; /* last register written */\r
60 int Volume[4]; /* volume of voice 0-2 and noise */\r
61 unsigned int RNG; /* noise generator */\r
62 int NoiseFB; /* noise feedback mask */\r
63 int Period[4];\r
64 int Count[4];\r
65 int Output[4];\r
66 int pad[1];\r
67};\r
68\r
69static struct SN76496 ono_sn; // one and only SN76496\r
70int *sn76496_regs;\r
71\r
72//static\r
73void SN76496Write(int data)\r
74{\r
75 struct SN76496 *R = &ono_sn;\r
5103774f 76 int n, r, c;\r
cc68a136 77\r
78 /* update the output buffer before changing the registers */\r
79 //stream_update(R->Channel,0);\r
80\r
5103774f 81 r = R->LastRegister;\r
cc68a136 82 if (data & 0x80)\r
5103774f 83 r = R->LastRegister = (data & 0x70) >> 4;\r
84 c = r / 2;\r
cc68a136 85\r
5103774f 86 if (!(data & 0x80) && (r == 0 || r == 2 || r == 4))\r
87 // data byte (tone only)\r
88 R->Register[r] = (R->Register[r] & 0x0f) | ((data & 0x3f) << 4);\r
cc68a136 89 else\r
5103774f 90 R->Register[r] = (R->Register[r] & 0x3f0) | (data & 0x0f);\r
cc68a136 91\r
5103774f 92 data = R->Register[r];\r
93 switch (r)\r
94 {\r
95 case 0: /* tone 0 : frequency */\r
96 case 2: /* tone 1 : frequency */\r
97 case 4: /* tone 2 : frequency */\r
98 R->Period[c] = R->UpdateStep * data;\r
99 if (R->Period[c] == 0) R->Period[c] = R->UpdateStep;\r
100 if (r == 4)\r
101 {\r
102 /* update noise shift frequency */\r
103 if ((R->Register[6] & 0x03) == 0x03)\r
104 R->Period[3] = 2 * R->Period[2];\r
105 }\r
106 break;\r
107 case 1: /* tone 0 : volume */\r
108 case 3: /* tone 1 : volume */\r
109 case 5: /* tone 2 : volume */\r
110 case 7: /* noise : volume */\r
111 R->Volume[c] = R->VolTable[data & 0x0f];\r
112 break;\r
113 case 6: /* noise : frequency, mode */\r
114 n = data;\r
115 R->NoiseFB = (n & 4) ? FB_WNOISE : FB_PNOISE;\r
116 n &= 3;\r
117 /* N/512,N/1024,N/2048,Tone #3 output */\r
118 R->Period[3] = (n == 3) ? 2 * R->Period[2] : (R->UpdateStep << (5 + n));\r
119\r
120 /* reset noise shifter */\r
121 R->RNG = NG_PRESET;\r
122 R->Output[3] = R->RNG & 1;\r
123 break;\r
cc68a136 124 }\r
125}\r
126\r
127/*\r
128WRITE8_HANDLER( SN76496_0_w ) { SN76496Write(0,data); }\r
129WRITE8_HANDLER( SN76496_1_w ) { SN76496Write(1,data); }\r
130WRITE8_HANDLER( SN76496_2_w ) { SN76496Write(2,data); }\r
131WRITE8_HANDLER( SN76496_3_w ) { SN76496Write(3,data); }\r
132WRITE8_HANDLER( SN76496_4_w ) { SN76496Write(4,data); }\r
133*/\r
134\r
135//static\r
4f265db7 136void SN76496Update(short *buffer, int length, int stereo)\r
cc68a136 137{\r
138 int i;\r
139 struct SN76496 *R = &ono_sn;\r
140\r
141 /* If the volume is 0, increase the counter */\r
142 for (i = 0;i < 4;i++)\r
143 {\r
144 if (R->Volume[i] == 0)\r
145 {\r
146 /* note that I do count += length, NOT count = length + 1. You might think */\r
147 /* it's the same since the volume is 0, but doing the latter could cause */\r
148 /* interferencies when the program is rapidly modulating the volume. */\r
149 if (R->Count[i] <= length*STEP) R->Count[i] += length*STEP;\r
150 }\r
151 }\r
152\r
153 while (length > 0)\r
154 {\r
155 int vol[4];\r
156 unsigned int out;\r
157 int left;\r
158\r
159\r
160 /* vol[] keeps track of how long each square wave stays */\r
161 /* in the 1 position during the sample period. */\r
162 vol[0] = vol[1] = vol[2] = vol[3] = 0;\r
163\r
164 for (i = 0;i < 3;i++)\r
165 {\r
166 if (R->Output[i]) vol[i] += R->Count[i];\r
167 R->Count[i] -= STEP;\r
168 /* Period[i] is the half period of the square wave. Here, in each */\r
169 /* loop I add Period[i] twice, so that at the end of the loop the */\r
170 /* square wave is in the same status (0 or 1) it was at the start. */\r
171 /* vol[i] is also incremented by Period[i], since the wave has been 1 */\r
172 /* exactly half of the time, regardless of the initial position. */\r
173 /* If we exit the loop in the middle, Output[i] has to be inverted */\r
174 /* and vol[i] incremented only if the exit status of the square */\r
175 /* wave is 1. */\r
176 while (R->Count[i] <= 0)\r
177 {\r
178 R->Count[i] += R->Period[i];\r
179 if (R->Count[i] > 0)\r
180 {\r
181 R->Output[i] ^= 1;\r
182 if (R->Output[i]) vol[i] += R->Period[i];\r
183 break;\r
184 }\r
185 R->Count[i] += R->Period[i];\r
186 vol[i] += R->Period[i];\r
187 }\r
188 if (R->Output[i]) vol[i] -= R->Count[i];\r
189 }\r
190\r
191 left = STEP;\r
192 do\r
193 {\r
194 int nextevent;\r
195\r
196 if (R->Count[3] < left) nextevent = R->Count[3];\r
197 else nextevent = left;\r
198\r
199 if (R->Output[3]) vol[3] += R->Count[3];\r
200 R->Count[3] -= nextevent;\r
201 if (R->Count[3] <= 0)\r
202 {\r
203 if (R->RNG & 1) R->RNG ^= R->NoiseFB;\r
204 R->RNG >>= 1;\r
205 R->Output[3] = R->RNG & 1;\r
206 R->Count[3] += R->Period[3];\r
207 if (R->Output[3]) vol[3] += R->Period[3];\r
208 }\r
209 if (R->Output[3]) vol[3] -= R->Count[3];\r
210\r
211 left -= nextevent;\r
212 } while (left > 0);\r
213\r
214 out = vol[0] * R->Volume[0] + vol[1] * R->Volume[1] +\r
215 vol[2] * R->Volume[2] + vol[3] * R->Volume[3];\r
216\r
217 if (out > MAX_OUTPUT * STEP) out = MAX_OUTPUT * STEP;\r
218\r
4f265db7 219 if ((out /= STEP)) // will be optimized to shift; max 0x47ff = 18431\r
cc68a136 220 *buffer += out;\r
4f265db7 221 if(stereo) buffer+=2; // only left for stereo, to be mixed to right later\r
222 else buffer++;\r
cc68a136 223\r
224 length--;\r
225 }\r
226}\r
227\r
228\r
229static void SN76496_set_clock(struct SN76496 *R,int clock)\r
230{\r
231\r
232 /* the base clock for the tone generators is the chip clock divided by 16; */\r
233 /* for the noise generator, it is clock / 256. */\r
234 /* Here we calculate the number of steps which happen during one sample */\r
235 /* at the given sample rate. No. of events = sample rate / (clock/16). */\r
236 /* STEP is a multiplier used to turn the fraction into a fixed point */\r
237 /* number. */\r
238 R->UpdateStep = ((double)STEP * R->SampleRate * 16) / clock;\r
239}\r
240\r
241\r
242static void SN76496_set_gain(struct SN76496 *R,int gain)\r
243{\r
244 int i;\r
245 double out;\r
246\r
247\r
248 gain &= 0xff;\r
249\r
250 /* increase max output basing on gain (0.2 dB per step) */\r
251 out = MAX_OUTPUT / 3;\r
252 while (gain-- > 0)\r
253 out *= 1.023292992; /* = (10 ^ (0.2/20)) */\r
254\r
255 /* build volume table (2dB per step) */\r
256 for (i = 0;i < 15;i++)\r
257 {\r
258 /* limit volume to avoid clipping */\r
259 if (out > MAX_OUTPUT / 3) R->VolTable[i] = MAX_OUTPUT / 3;\r
260 else R->VolTable[i] = out;\r
261\r
262 out /= 1.258925412; /* = 10 ^ (2/20) = 2dB */\r
263 }\r
264 R->VolTable[15] = 0;\r
265}\r
266\r
267\r
268//static\r
269int SN76496_init(int clock,int sample_rate)\r
270{\r
271 struct SN76496 *R = &ono_sn;\r
272 int i;\r
273\r
274 //R->Channel = stream_create(0,1, sample_rate,R,SN76496Update);\r
275 sn76496_regs = R->Register;\r
276\r
277 R->SampleRate = sample_rate;\r
278 SN76496_set_clock(R,clock);\r
279\r
280 for (i = 0;i < 4;i++) R->Volume[i] = 0;\r
281\r
282 R->LastRegister = 0;\r
283 for (i = 0;i < 8;i+=2)\r
284 {\r
285 R->Register[i] = 0;\r
286 R->Register[i + 1] = 0x0f; /* volume = 0 */\r
287 }\r
288\r
289 for (i = 0;i < 4;i++)\r
290 {\r
291 R->Output[i] = 0;\r
292 R->Period[i] = R->Count[i] = R->UpdateStep;\r
293 }\r
294 R->RNG = NG_PRESET;\r
295 R->Output[3] = R->RNG & 1;\r
296\r
297 // added\r
298 SN76496_set_gain(R, 0);\r
299\r
300 return 0;\r
301}\r
302\r