1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /*****************************************************************************
4
5 Harris HC-55516 (and related) emulator
6
7 FBAlpha port by dink, July 2018
8
9 *****************************************************************************/
10
11 #include "burnint.h"
12 #include "hc55516.h"
13 #include <math.h>
14
15 #define SAMPLE_RATE (48000)
16 #define FRAME_SIZE 800
17
18 #define INTEGRATOR_LEAK_TC 0.001
19 #define FILTER_DECAY_TC 0.004
20 #define FILTER_CHARGE_TC 0.004
21 #define FILTER_MIN 0.0416
22 #define FILTER_MAX 1.0954
23 #define SAMPLE_GAIN 10000.0
24
25 static INT32 m_active_clock_hi;
26 static UINT8 m_shiftreg_mask;
27
28 static UINT8 m_last_clock_state;
29 static UINT8 m_digit;
30 static UINT8 m_new_digit;
31 static UINT8 m_shiftreg;
32
33 static INT16 m_curr_sample;
34 static INT16 m_next_sample;
35
36 static UINT32 m_update_count;
37
38 static double m_filter;
39 static double m_integrator;
40
41 static double m_charge;
42 static double m_decay;
43 static double m_leak;
44
45 static INT32 m_clock = 0; // always 0 for sw-driven clock
46
47 static INT16 *m_mixer_buffer; // re-sampler
48
49 static INT32 (*pCPUTotalCycles)() = NULL;
50 static UINT32 nDACCPUMHZ = 0;
51 static INT32 nCurrentPosition = 0;
52
53 static void hc55516_update_int(INT16 *inputs, INT32 sample_len);
54
55
56 // Streambuffer handling
SyncInternal()57 static INT32 SyncInternal()
58 {
59 return (INT32)(float)(FRAME_SIZE * (pCPUTotalCycles() / (nDACCPUMHZ / (nBurnFPS / 100.0000))));
60 }
61
UpdateStream(INT32 length)62 static void UpdateStream(INT32 length)
63 {
64 if (length > FRAME_SIZE) length = FRAME_SIZE;
65
66 length -= nCurrentPosition;
67 if (length <= 0) return;
68
69 INT16 *lbuf = m_mixer_buffer + nCurrentPosition;
70
71 hc55516_update_int(lbuf, length);
72
73 nCurrentPosition += length;
74 }
75
76 static void start_common(UINT8 _shiftreg_mask, INT32 _active_clock_hi);
77
78 //-------------------------------------------------
79 // device_start - device-specific startup
80 //-------------------------------------------------
81
hc55516_init(INT32 (* pCPUCyclesCB)(),INT32 nCpuMHZ)82 void hc55516_init(INT32 (*pCPUCyclesCB)(), INT32 nCpuMHZ)
83 {
84 pCPUTotalCycles = pCPUCyclesCB;
85 nDACCPUMHZ = nCpuMHZ;
86
87 start_common(0x07, true);
88 }
89
90 #if 0
91 // maybe we'll need these in the future, who knows?
92 void mc3417_init(INT32 (*pCPUCyclesCB)(), INT32 nCpuMHZ)
93 {
94 pCPUTotalCycles = pCPUCyclesCB;
95 nDACCPUMHZ = nCpuMHZ;
96
97 start_common(0x07, false);
98 }
99
100 void mc3418_init(INT32 (*pCPUCyclesCB)(), INT32 nCpuMHZ)
101 {
102 pCPUTotalCycles = pCPUCyclesCB;
103 nDACCPUMHZ = nCpuMHZ;
104
105 start_common(0x0f, false);
106 }
107 #endif
108
hc55516_exit()109 void hc55516_exit()
110 {
111 BurnFree(m_mixer_buffer);
112 }
113
114 //-------------------------------------------------
115 // device_reset - device-specific reset
116 //-------------------------------------------------
117
hc55516_reset()118 void hc55516_reset()
119 {
120 m_last_clock_state = 0;
121 m_digit = 0;
122 m_new_digit = 0;
123 m_shiftreg = 0;
124
125 m_curr_sample = 0;
126 m_next_sample = 0;
127
128 m_update_count = 0;
129
130 m_filter = 0;
131 m_integrator = 0;
132
133 nCurrentPosition = 0;
134 }
135
hc55516_scan(INT32 nAction,INT32 *)136 void hc55516_scan(INT32 nAction, INT32 *)
137 {
138 SCAN_VAR(m_last_clock_state);
139 SCAN_VAR(m_digit);
140 SCAN_VAR(m_new_digit);
141 SCAN_VAR(m_shiftreg);
142
143 SCAN_VAR(m_curr_sample);
144 SCAN_VAR(m_next_sample);
145
146 SCAN_VAR(m_update_count);
147
148 SCAN_VAR(m_filter);
149 SCAN_VAR(m_integrator);
150 }
151
152 //-------------------------------------------------
153 // device_start - device-specific startup
154 //-------------------------------------------------
155
start_common(UINT8 _shiftreg_mask,INT32 _active_clock_hi)156 static void start_common(UINT8 _shiftreg_mask, INT32 _active_clock_hi)
157 {
158 /* compute the fixed charge, decay, and leak time constants */
159 m_charge = pow(exp(-1.0), 1.0 / (FILTER_CHARGE_TC * 16000.0));
160 m_decay = pow(exp(-1.0), 1.0 / (FILTER_DECAY_TC * 16000.0));
161 m_leak = pow(exp(-1.0), 1.0 / (INTEGRATOR_LEAK_TC * 16000.0));
162
163 m_shiftreg_mask = _shiftreg_mask;
164 m_active_clock_hi = _active_clock_hi;
165
166 m_mixer_buffer = (INT16*)BurnMalloc(2 * sizeof(INT16) * SAMPLE_RATE);
167 }
168
is_external_oscillator()169 static inline INT32 is_external_oscillator()
170 {
171 return m_clock != 0;
172 }
173
174
is_active_clock_transition(INT32 clock_state)175 static inline INT32 is_active_clock_transition(INT32 clock_state)
176 {
177 return (( m_active_clock_hi && !m_last_clock_state && clock_state) ||
178 (!m_active_clock_hi && m_last_clock_state && !clock_state));
179 }
180
181
current_clock_state()182 static inline INT32 current_clock_state()
183 {
184 return ((UINT64)m_update_count * m_clock * 2 / SAMPLE_RATE) & 0x01;
185 }
186
187
process_digit()188 static void process_digit()
189 {
190 double integrator = m_integrator, temp;
191
192 /* shift the bit into the shift register */
193 m_shiftreg = (m_shiftreg << 1) | m_digit;
194
195 /* move the estimator up or down a step based on the bit */
196 if (m_digit)
197 integrator += m_filter;
198 else
199 integrator -= m_filter;
200
201 /* simulate leakage */
202 integrator *= m_leak;
203
204 /* if we got all 0's or all 1's in the last n bits, bump the step up */
205 if (((m_shiftreg & m_shiftreg_mask) == 0) ||
206 ((m_shiftreg & m_shiftreg_mask) == m_shiftreg_mask))
207 {
208 m_filter = FILTER_MAX - ((FILTER_MAX - m_filter) * m_charge);
209
210 if (m_filter > FILTER_MAX)
211 m_filter = FILTER_MAX;
212 }
213
214 /* simulate decay */
215 else
216 {
217 m_filter *= m_decay;
218
219 if (m_filter < FILTER_MIN)
220 m_filter = FILTER_MIN;
221 }
222
223 /* compute the sample as a 32-bit word */
224 temp = integrator * SAMPLE_GAIN;
225 m_integrator = integrator;
226
227 /* compress the sample range to fit better in a 16-bit word */
228 if (temp < 0)
229 m_next_sample = (int)(temp / (-temp * (1.0 / 32768.0) + 1.0));
230 else
231 m_next_sample = (int)(temp / (temp * (1.0 / 32768.0) + 1.0));
232 }
233
hc55516_clock_w(INT32 state)234 void hc55516_clock_w(INT32 state)
235 {
236 UINT8 clock_state = state ? true : false;
237
238 /* only makes sense for setups with a software driven clock */
239 //assert(!is_external_oscillator());
240
241 /* speech clock changing? */
242 if (is_active_clock_transition(clock_state))
243 {
244 /* update the output buffer before changing the registers */
245 UpdateStream(SyncInternal());
246
247 /* clear the update count */
248 m_update_count = 0;
249
250 process_digit();
251 }
252
253 /* update the clock */
254 m_last_clock_state = clock_state;
255 }
256
257
hc55516_digit_w(INT32 digit)258 void hc55516_digit_w(INT32 digit)
259 {
260 if (is_external_oscillator())
261 {
262 UpdateStream(SyncInternal());
263 m_new_digit = digit & 1;
264 }
265 else
266 m_digit = digit & 1;
267 }
268
269
hc55516_clock_state_r()270 INT32 hc55516_clock_state_r()
271 {
272 /* only makes sense for setups with an external oscillator */
273 //assert(is_external_oscillator());
274
275 UpdateStream(SyncInternal());
276
277 return current_clock_state();
278 }
279
280
281 //-------------------------------------------------
282 // sound_stream_update - handle a stream update
283 //-------------------------------------------------
284
hc55516_update_int(INT16 * inputs,INT32 samples)285 static void hc55516_update_int(INT16 *inputs, INT32 samples)
286 {
287 INT16 *buffer = inputs;
288 INT32 i;
289 INT32 sample, slope;
290
291 /* zero-length? bail */
292 if (samples == 0)
293 return;
294
295 if (!is_external_oscillator())
296 {
297 /* track how many samples we've updated without a clock */
298 m_update_count += samples;
299 if (m_update_count > SAMPLE_RATE / 32)
300 {
301 m_update_count = SAMPLE_RATE;
302 m_next_sample = 0;
303 }
304 }
305
306 /* compute the interpolation slope */
307 sample = m_curr_sample;
308 slope = ((INT32)m_next_sample - sample) / samples;
309 m_curr_sample = m_next_sample;
310
311 if (is_external_oscillator())
312 {
313 /* external oscillator */
314 for (i = 0; i < samples; i++, sample += slope)
315 {
316 UINT8 clock_state;
317
318 *buffer = sample;
319 buffer++;
320
321 m_update_count++;
322
323 clock_state = current_clock_state();
324
325 /* pull in next digit on the appropriate edge of the clock */
326 if (is_active_clock_transition(clock_state))
327 {
328 m_digit = m_new_digit;
329
330 process_digit();
331 }
332
333 m_last_clock_state = clock_state;
334 }
335 }
336
337 /* software driven clock */
338 else
339 for (i = 0; i < samples; i++, sample += slope) {
340 *buffer = sample;
341 buffer++;
342 }
343 }
344
hc55516_update(INT16 * inputs,INT32 sample_len)345 void hc55516_update(INT16 *inputs, INT32 sample_len)
346 {
347 if (sample_len != nBurnSoundLen) {
348 bprintf(PRINT_ERROR, _T("*** hc55516_update(): call once per frame!\n"));
349 return;
350 }
351
352 INT32 samples_from = (INT32)((double)((SAMPLE_RATE * 100) / nBurnFPS) + 0.5);
353
354 UpdateStream(samples_from);
355
356 for (INT32 j = 0; j < sample_len; j++)
357 {
358 INT32 k = (samples_from * j) / nBurnSoundLen;
359
360 INT32 rlmono = m_mixer_buffer[k];
361
362 inputs[0] = BURN_SND_CLIP(inputs[0] + BURN_SND_CLIP(rlmono));
363 inputs[1] = BURN_SND_CLIP(inputs[1] + BURN_SND_CLIP(rlmono));
364 inputs += 2;
365 }
366
367 memset(m_mixer_buffer, 0, samples_from * sizeof(INT16));
368 nCurrentPosition = 0;
369 }
370