summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/s14001a.cpp
blob: 627d3214b10cd7d2c328e0007d94123ece47c6dd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
// license:BSD-3-Clause
// copyright-holders:Ed Bernard, Jonathan Gevaryahu, hap
// thanks-to:Kevin Horton
/*
    SSi TSI S14001A speech IC emulator
    aka CRC: Custom ROM Controller, designed in 1975, first usage in 1976 on TSI Speech+ calculator
    Originally written for MAME by Jonathan Gevaryahu(Lord Nightmare) 2006-2013,
    replaced with near-complete rewrite by Ed Bernard in 2016

    TODO:
    - nothing at the moment?

    Further reading:
    - http://www.vintagecalculators.com/html/speech-.html
    - http://www.vintagecalculators.com/html/development_of_the_tsi_speech-.html
    - http://www.vintagecalculators.com/html/speech-_state_machine.html
    - https://archive.org/stream/pdfy-QPCSwTWiFz1u9WU_/david_djvu.txt
*/

/* Chip Pinout:
The original datasheet (which is lost as far as I know) clearly called the
s14001a chip the 'CRC chip', or 'Custom Rom Controller', as it appears with
this name on the Stern and Canon schematics, as well as on some TSI speech
print advertisements.
Labels are not based on the labels used by the Atari wolf pack and Stern
schematics, as these are inconsistent. Atari calls the word select/speech address
input pins SAx while Stern calls them Cx. Also Atari and Canon both have the bit
ordering for the word select/speech address bus backwards, which may indicate it
was so on the original datasheet. Stern has it correct, and I've used their Cx
labeling.

                      ______    ______
                    _|o     \__/      |_
            +5V -- |_|1             40|_| -> /BUSY*
                    _|                |_
          ?TEST ?? |_|2             39|_| <- ROM D7
                    _|                |_
 XTAL CLOCK/CKC -> |_|3             38|_| -> ROM A11
                    _|                |_
  ROM CLOCK/CKR <- |_|4             37|_| <- ROM D6
                    _|                |_
  DIGITAL OUT 0 <- |_|5             36|_| -> ROM A10
                    _|                |_
  DIGITAL OUT 1 <- |_|6             35|_| -> ROM A9
                    _|                |_
  DIGITAL OUT 2 <- |_|7             34|_| <- ROM D5
                    _|                |_
  DIGITAL OUT 3 <- |_|8             33|_| -> ROM A8
                    _|                |_
        ROM /EN <- |_|9             32|_| <- ROM D4
                    _|       S        |_
          START -> |_|10 7   1   T  31|_| -> ROM A7
                    _|   7   4   S    |_
      AUDIO OUT <- |_|11 3   0   I  30|_| <- ROM D3
                    _|   7   0        |_
         ROM A0 <- |_|12     1      29|_| -> ROM A6
                    _|       A        |_
SPCH ADR BUS C0 -> |_|13            28|_| <- SPCH ADR BUS C5
                    _|                |_
         ROM A1 <- |_|14            27|_| <- ROM D2
                    _|                |_
SPCH ADR BUS C1 -> |_|15            26|_| <- SPCH ADR BUS C4
                    _|                |_
         ROM A2 <- |_|16            25|_| <- ROM D1
                    _|                |_
SPCH ADR BUS C2 -> |_|17            24|_| <- SPCH ADR BUS C3
                    _|                |_
         ROM A3 <- |_|18            23|_| <- ROM D0
                    _|                |_
         ROM A4 <- |_|19            22|_| -> ROM A5
                    _|                |_
            GND -- |_|20            21|_| -- -10V
                     |________________|

*Note from Kevin Horton when testing the hookup of the S14001A: the /BUSY line
is not a standard voltage line: when it is in its HIGH state (i.e. not busy) it
puts out a voltage of -10 volts, so it needs to be dropped back to a sane
voltage level before it can be passed to any sort of modern IC. The address
lines for the speech rom (A0-A11) do not have this problem, they output at a
TTL/CMOS compatible voltage. The AUDIO OUT pin also outputs a voltage below GND,
and the TEST pins may do so too.

START is pulled high when a word is to be said and the word number is on the
word select/speech address input lines. The Canon 'Canola' uses a separate 'rom
strobe' signal independent of the chip to either enable or clock the speech rom.
It's likely that they did this to be able to force the speech chip to stop talking,
which is normally impossible. The later 'version 3' TSI speech board as featured in
an advertisement in the John Cater book probably also has this feature, in addition
to external speech rom banking.

The Digital out pins supply a copy of the 4-bit waveform which also goes to the
internal DAC. They are only valid every other clock cycle. It is possible that
on 'invalid' cycles they act as a 4 bit input to drive the dac.

Because it requires -10V to operate, the chip manufacturing process must be PMOS.

* Operation:
Put the 6-bit address of the word to be said onto the C0-C5 word select/speech
address bus lines. Next, clock the START line low-high-low. As long as the START
line is held high, the first address byte of the first word will be read repeatedly
every clock, with the rom enable line enabled constantly (i.e. it doesn't toggle on
and off as it normally does during speech). Once START has gone low-high-low, the
/BUSY line will go low until 3 clocks after the chip is done speaking.
*/

#include "emu.h"
#include "s14001a.h"

// device definition
const device_type S14001A = &device_creator<s14001a_device>;

s14001a_device::s14001a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, S14001A, "S14001A", tag, owner, clock, "s14001a", __FILE__),
		device_sound_interface(mconfig, *this),
		m_SpeechRom(*this, DEVICE_SELF),
		m_stream(nullptr),
		m_bsy_handler(*this),
		m_ext_read_handler(*this)
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

ALLOW_SAVE_TYPE(s14001a_device::states); // allow save_item on a non-fundamental type

void s14001a_device::device_start()
{
	m_stream = machine().sound().stream_alloc(*this, 0, 1, clock() ? clock() : machine().sample_rate());

	// resolve callbacks
	m_ext_read_handler.resolve();
	m_bsy_handler.resolve();

	// note: zerofill is done already by MAME core
	ClearStatistics();
	m_uOutputP1 = m_uOutputP2 = 7;

	// register for savestates
	save_item(NAME(m_bPhase1));
	save_item(NAME(m_uStateP1));
	save_item(NAME(m_uStateP2));
	save_item(NAME(m_uDAR13To05P1));
	save_item(NAME(m_uDAR13To05P2));
	save_item(NAME(m_uDAR04To00P1));
	save_item(NAME(m_uDAR04To00P2));
	save_item(NAME(m_uCWARP1));
	save_item(NAME(m_uCWARP2));

	save_item(NAME(m_bStopP1));
	save_item(NAME(m_bStopP2));
	save_item(NAME(m_bVoicedP1));
	save_item(NAME(m_bVoicedP2));
	save_item(NAME(m_bSilenceP1));
	save_item(NAME(m_bSilenceP2));
	save_item(NAME(m_uLengthP1));
	save_item(NAME(m_uLengthP2));
	save_item(NAME(m_uXRepeatP1));
	save_item(NAME(m_uXRepeatP2));
	save_item(NAME(m_uDeltaOldP1));
	save_item(NAME(m_uDeltaOldP2));
	save_item(NAME(m_uOutputP1));

	save_item(NAME(m_bDAR04To00CarryP2));
	save_item(NAME(m_bPPQCarryP2));
	save_item(NAME(m_bRepeatCarryP2));
	save_item(NAME(m_bLengthCarryP2));
	save_item(NAME(m_RomAddrP1));

	save_item(NAME(m_uOutputP2));
	save_item(NAME(m_uRomAddrP2));
	save_item(NAME(m_bBusyP1));
	save_item(NAME(m_bStart));
	save_item(NAME(m_uWord));

	save_item(NAME(m_uNPitchPeriods));
	save_item(NAME(m_uNVoiced));
	save_item(NAME(m_uNControlWords));
	save_item(NAME(m_uPrintLevel));
}


//-------------------------------------------------
//  sound_stream_update - handle a stream update
//-------------------------------------------------

void s14001a_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	for (int i = 0; i < samples; i++)
	{
		Clock();
		INT16 sample = m_uOutputP2 - 7; // range -7..8
		outputs[0][i] = sample * 0xf00;
	}
}


/**************************************************************************
    External interface
**************************************************************************/

void s14001a_device::force_update()
{
	m_stream->update();
}

READ_LINE_MEMBER(s14001a_device::romen_r)
{
	m_stream->update();
	return (m_bPhase1) ? 1 : 0;
}

READ_LINE_MEMBER(s14001a_device::busy_r)
{
	m_stream->update();
	return (m_bBusyP1) ? 1 : 0;
}

WRITE8_MEMBER(s14001a_device::data_w)
{
	m_stream->update();
	m_uWord = data & 0x3f; // C0-C5
}

WRITE_LINE_MEMBER(s14001a_device::start_w)
{
	m_stream->update();
	m_bStart = (state != 0);
	if (m_bStart) m_uStateP1 = WORDWAIT;
}

void s14001a_device::set_clock(UINT32 clock)
{
	m_stream->update();
	m_stream->set_sample_rate(clock);
}


/**************************************************************************
    Device emulation
**************************************************************************/

UINT8 s14001a_device::readmem(UINT16 offset, bool phase)
{
	offset &= 0xfff; // 11-bit internal
	return ((m_ext_read_handler.isnull()) ? m_SpeechRom[offset & (m_SpeechRom.bytes() - 1)] : m_ext_read_handler(offset));
}

bool s14001a_device::Clock()
{
	// effectively toggles external clock twice, one cycle
	// internal clock toggles on external clock transition from 0 to 1 so internal clock will always transition here
	// return false if some emulator problem detected

	// On the actual chip, all register phase 1 values needed to be refreshed from phase 2 values
	// or else risk losing their state due to charge loss.
	// But on a computer the values are static.
	// So to reduce code clutter, phase 1 values are only modified if they are different
	// from the preceeding phase 2 values.

	if (m_bPhase1)
	{
		// transition to phase2
		m_bPhase1 = false;

		// transfer phase1 variables to phase2
		m_uStateP2     = m_uStateP1;
		m_uDAR13To05P2 = m_uDAR13To05P1;
		m_uDAR04To00P2 = m_uDAR04To00P1;
		m_uCWARP2      = m_uCWARP1;
		m_bStopP2      = m_bStopP1;
		m_bVoicedP2    = m_bVoicedP1;
		m_bSilenceP2   = m_bSilenceP1;
		m_uLengthP2    = m_uLengthP1;
		m_uXRepeatP2   = m_uXRepeatP1;
		m_uDeltaOldP2  = m_uDeltaOldP1;

		m_uOutputP2    = m_uOutputP1;
		m_uRomAddrP2   = m_RomAddrP1;

		// setup carries from phase 2 values
		m_bDAR04To00CarryP2  = m_uDAR04To00P2 == 0x1F;
		m_bPPQCarryP2        = m_bDAR04To00CarryP2 && ((m_uLengthP2&0x03) == 0x03); // pitch period quarter
		m_bRepeatCarryP2     = m_bPPQCarryP2       && ((m_uLengthP2&0x0C) == 0x0C);
		m_bLengthCarryP2     = m_bRepeatCarryP2    && ( m_uLengthP2       == 0x7F);

		return true;
	}
	m_bPhase1 = true;

	// logic done during phase 1
	switch (m_uStateP1)
	{
	case IDLE:
		m_uOutputP1 = 7;
		if (m_bStart) m_uStateP1 = WORDWAIT;

		if (m_bBusyP1 && !m_bsy_handler.isnull())
			m_bsy_handler(0);
		m_bBusyP1 = false;
		break;

	case WORDWAIT:
		// the delta address register latches the word number into bits 03 to 08
		// all other bits forced to 0.  04 to 08 makes a multiply by two.
		m_uDAR13To05P1 = (m_uWord&0x3C)>>2;
		m_uDAR04To00P1 = (m_uWord&0x03)<<3;
		m_RomAddrP1 = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits
		m_uOutputP1 = 7;
		if (m_bStart) m_uStateP1 = WORDWAIT;
		else          m_uStateP1 = CWARMSB;

		if (!m_bBusyP1 && !m_bsy_handler.isnull())
			m_bsy_handler(1);
		m_bBusyP1 = true;
		break;

	case CWARMSB:
		if (m_uPrintLevel >= 1)
			printf("\n speaking word %02x",m_uWord);

		// use uDAR to load uCWAR 8 msb
		m_uCWARP1 = readmem(m_uRomAddrP2,m_bPhase1)<<4; // note use of rom address setup in previous state
		// increment DAR by 4, 2 lsb's count deltas within a byte
		m_uDAR04To00P1 += 4;
		if (m_uDAR04To00P1 >= 32) m_uDAR04To00P1 = 0; // emulate 5 bit counter
		m_RomAddrP1 = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits

		m_uOutputP1 = 7;
		if (m_bStart) m_uStateP1 = WORDWAIT;
		else          m_uStateP1 = CWARLSB;
		break;

	case CWARLSB:
		m_uCWARP1   = m_uCWARP2|(readmem(m_uRomAddrP2,m_bPhase1)>>4); // setup in previous state
		m_RomAddrP1 = m_uCWARP1;

		m_uOutputP1 = 7;
		if (m_bStart) m_uStateP1 = WORDWAIT;
		else          m_uStateP1 = DARMSB;
		break;

	case DARMSB:
		m_uDAR13To05P1 = readmem(m_uRomAddrP2,m_bPhase1)<<1; // 9 bit counter, 8 MSBs from ROM, lsb zeroed
		m_uDAR04To00P1 = 0;
		m_uCWARP1++;
		m_RomAddrP1 = m_uCWARP1;
		m_uNControlWords++; // statistics

		m_uOutputP1 = 7;
		if (m_bStart) m_uStateP1 = WORDWAIT;
		else          m_uStateP1 = CTRLBITS;
		break;

	case CTRLBITS:
		m_bStopP1    = readmem(m_uRomAddrP2,m_bPhase1)&0x80? true: false;
		m_bVoicedP1  = readmem(m_uRomAddrP2,m_bPhase1)&0x40? true: false;
		m_bSilenceP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x20? true: false;
		m_uXRepeatP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x03;
		m_uLengthP1  =(readmem(m_uRomAddrP2,m_bPhase1)&0x1F)<<2; // includes external length and repeat
		m_uDAR04To00P1 = 0;
		m_uCWARP1++; // gets ready for next DARMSB
		m_RomAddrP1  = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits

		m_uOutputP1 = 7;
		if (m_bStart) m_uStateP1 = WORDWAIT;
		else          m_uStateP1 = PLAY;

		if (m_uPrintLevel >= 2)
			printf("\n cw %d %d %d %d %d",m_bStopP1,m_bVoicedP1,m_bSilenceP1,m_uLengthP1>>4,m_uXRepeatP1);

		break;

	case PLAY:
	{
		// statistics
		if (m_bPPQCarryP2)
		{
			// pitch period end
			if (m_uPrintLevel >= 3)
				printf("\n ppe: RomAddr %03x",m_uRomAddrP2);

			m_uNPitchPeriods++;
			if (m_bVoicedP2) m_uNVoiced++;
		}
		// end statistics

		// modify output
		UINT8 uDeltaP2;     // signal line
		UINT8 uIncrementP2; // signal lines
		bool bAddP2;        // signal line
		uDeltaP2 = Mux8To2(m_bVoicedP2,
					m_uLengthP2 & 0x03,     // pitch period quater counter
					m_uDAR04To00P2 & 0x03,  // two bit delta address within byte
					readmem(m_uRomAddrP2,m_bPhase1)
		);
		CalculateIncrement(m_bVoicedP2,
					m_uLengthP2 & 0x03,     // pitch period quater counter
					m_uDAR04To00P2 == 0,    // pitch period quarter start
					uDeltaP2,
					m_uDeltaOldP2,          // input
					m_uDeltaOldP1,          // output
					uIncrementP2,           // output 0, 1, or 3
					bAddP2                  // output
		);
		m_uOutputP1 = CalculateOutput(m_bVoicedP2,
					m_bSilenceP2,
					m_uLengthP2 & 0x03,     // pitch period quater counter
					m_uDAR04To00P2 == 0,    // pitch period quarter start
					m_uOutputP2,            // last output
					uIncrementP2,
					bAddP2
		);

		// advance counters
		m_uDAR04To00P1++;
		if (m_bDAR04To00CarryP2) // pitch period quarter end
		{
			m_uDAR04To00P1 = 0; // emulate 5 bit counter

			m_uLengthP1++; // lower two bits of length count quarter pitch periods
			if (m_uLengthP1 >= 0x80)
			{
				m_uLengthP1 = 0; // emulate 7 bit counter
			}
		}

		if (m_bVoicedP2 && m_bRepeatCarryP2) // repeat complete
		{
			m_uLengthP1 &= 0x70; // keep current "length"
			m_uLengthP1 |= (m_uXRepeatP1<<2); // load repeat from external repeat
			m_uDAR13To05P1++; // advances ROM address 8 bytes
			if (m_uDAR13To05P1 >= 0x200) m_uDAR13To05P1 = 0; // emulate 9 bit counter
		}
		if (!m_bVoicedP2 && m_bDAR04To00CarryP2)
		{
			// unvoiced advances each quarter pitch period
			// note repeat counter not reloaded for non voiced speech
			m_uDAR13To05P1++; // advances ROM address 8 bytes
			if (m_uDAR13To05P1 >= 0x200) m_uDAR13To05P1 = 0; // emulate 9 bit counter
		}

		// construct m_RomAddrP1
		m_RomAddrP1 = m_uDAR04To00P1;
		if (m_bVoicedP2 && m_uLengthP1&0x1) // mirroring
		{
			m_RomAddrP1 ^= 0x1f; // count backwards
		}
		m_RomAddrP1 = (m_uDAR13To05P1<<3) | m_RomAddrP1>>2;

		// next state
		if (m_bStart) m_uStateP1 = WORDWAIT;
		else if (m_bStopP2 && m_bLengthCarryP2) m_uStateP1 = DELAY;
		else if (m_bLengthCarryP2)
		{
			m_uStateP1  = DARMSB;
			m_RomAddrP1 = m_uCWARP1; // output correct address
		}
		else m_uStateP1 = PLAY;
		break;
	}

	case DELAY:
		m_uOutputP1 = 7;
		if (m_bStart) m_uStateP1 = WORDWAIT;
		else          m_uStateP1 = IDLE;
		break;
	}

	return true;
}

UINT8 s14001a_device::Mux8To2(bool bVoicedP2, UINT8 uPPQtrP2, UINT8 uDeltaAdrP2, UINT8 uRomDataP2)
{
	// pick two bits of rom data as delta

	if (bVoicedP2 && uPPQtrP2&0x01) // mirroring
	{
		uDeltaAdrP2 ^= 0x03; // count backwards
	}
	// emulate 8 to 2 mux to obtain delta from byte (bigendian)
	switch (uDeltaAdrP2)
	{
	case 0x00:
		return (uRomDataP2&0xC0)>>6;
	case 0x01:
		return (uRomDataP2&0x30)>>4;
	case 0x02:
		return (uRomDataP2&0x0C)>>2;
	case 0x03:
		return (uRomDataP2&0x03)>>0;
	}
	return 0xFF;
}

void s14001a_device::CalculateIncrement(bool bVoicedP2, UINT8 uPPQtrP2, bool bPPQStartP2, UINT8 uDelta, UINT8 uDeltaOldP2, UINT8 &uDeltaOldP1, UINT8 &uIncrementP2, bool &bAddP2)
{
	// uPPQtr, pitch period quarter counter; 2 lsb of uLength
	// bPPStart, start of a pitch period
	// implemented to mimic silicon (a bit)

	// beginning of a pitch period
	if (uPPQtrP2 == 0x00 && bPPQStartP2) // note this is done for voiced and unvoiced
	{
		uDeltaOldP2 = 0x02;
	}
	static const UINT8 uIncrements[4][4] =
	{
	//    00  01  10  11
		{ 3,  3,  1,  1,}, // 00
		{ 1,  1,  0,  0,}, // 01
		{ 0,  0,  1,  1,}, // 10
		{ 1,  1,  3,  3 }, // 11
	};

#define MIRROR  (uPPQtrP2&0x01)

	// calculate increment from delta, always done even if silent to update uDeltaOld
	// in silicon a PLA determined 0,1,3 and add/subtract and passed uDelta to uDeltaOld
	if (!bVoicedP2 || !MIRROR)
	{
		uIncrementP2 = uIncrements[uDelta][uDeltaOldP2];
		bAddP2       = uDelta >= 0x02;
	}
	else
	{
		uIncrementP2 = uIncrements[uDeltaOldP2][uDelta];
		bAddP2       = uDeltaOldP2 < 0x02;
	}
	uDeltaOldP1 = uDelta;
	if (bVoicedP2 && bPPQStartP2 && MIRROR) uIncrementP2 = 0; // no change when first starting mirroring
}

UINT8 s14001a_device::CalculateOutput(bool bVoiced, bool bXSilence, UINT8 uPPQtr, bool bPPQStart, UINT8 uLOutput, UINT8 uIncrementP2, bool bAddP2)
{
	// implemented to mimic silicon (a bit)
	// limits output to 0x00 and 0x0f
	UINT8 uTmp; // used for subtraction

#define SILENCE (uPPQtr&0x02)

	// determine output
	if (bXSilence || (bVoiced && SILENCE)) return 7;

	// beginning of a pitch period
	if (uPPQtr == 0x00 && bPPQStart) // note this is done for voiced and nonvoiced
	{
		uLOutput = 7;
	}

	// adder
	uTmp = uLOutput;
	if (!bAddP2) uTmp ^= 0x0F; // turns subtraction into addition

	// add 0, 1, 3; limit at 15
	uTmp += uIncrementP2;
	if (uTmp > 15) uTmp = 15;

	if (!bAddP2) uTmp ^= 0x0F; // turns addition back to subtraction
	return uTmp;
}

void s14001a_device::ClearStatistics()
{
	m_uNPitchPeriods = 0;
	m_uNVoiced       = 0;
	m_uPrintLevel    = 0;
	m_uNControlWords = 0;
}

void s14001a_device::GetStatistics(UINT32 &uNPitchPeriods, UINT32 &uNVoiced, UINT32 uNControlWords)
{
	uNPitchPeriods = m_uNPitchPeriods;
	uNVoiced = m_uNVoiced;
	uNControlWords = m_uNControlWords;
}