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