summaryrefslogtreecommitdiffstats
path: root/src/devices/sound/tms5220.h
blob: f06777c3dae7a1e62e332d7e853a3abb1cb32c0a (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
// license:BSD-3-Clause
// copyright-holders:Frank Palazzolo, Aaron Giles, Jonathan Gevaryahu, Raphael Nabet, Couriersud, Michael Zapf
#ifndef MAME_SOUND_TMS5220_H
#define MAME_SOUND_TMS5220_H

#pragma once

#include "machine/spchrom.h"

/* HACK: if defined, uses impossibly perfect 'straight line' interpolation */
#undef TMS5220_PERFECT_INTERPOLATION_HACK

/* clock rate = 80 * output sample rate,     */
/* usually 640000 for 8000 Hz sample rate or */
/* usually 800000 for 10000 Hz sample rate.  */

/* IRQ callback function, active low, i.e. state=0 */
#define MCFG_TMS52XX_IRQ_HANDLER(_devcb) \
	devcb = &downcast<tms5220_device &>(*device).set_irq_handler(DEVCB_##_devcb);

/* Ready callback function, active low, i.e. state=0 */
#define MCFG_TMS52XX_READYQ_HANDLER(_devcb) \
	devcb = &downcast<tms5220_device &>(*device).set_readyq_handler(DEVCB_##_devcb);

/* old VSM handler, remove me! */
#define MCFG_TMS52XX_SPEECHROM(_tag) \
	downcast<tms5220_device &>(*device).set_speechrom_tag(_tag);

/* new VSM handler */
#define MCFG_TMS52XX_M0_CB(_devcb) \
	devcb = &downcast<tms5220_device &>(*device).set_m0_callback(DEVCB_##_devcb);

#define MCFG_TMS52XX_M1_CB(_devcb) \
	devcb = &downcast<tms5220_device &>(*device).set_m1_callback(DEVCB_##_devcb);

#define MCFG_TMS52XX_ADDR_CB(_devcb) \
	devcb = &downcast<tms5220_device &>(*device).set_addr_callback(DEVCB_##_devcb);

#define MCFG_TMS52XX_DATA_CB(_devcb) \
	devcb = &downcast<tms5220_device &>(*device).set_data_callback(DEVCB_##_devcb);

#define MCFG_TMS52XX_ROMCLK_CB(_devcb) \
	devcb = &downcast<tms5220_device &>(device).set_romclk_callback(DEVCB_##_devcb);

class tms5220_device : public device_t, public device_sound_interface
{
public:
	enum
	{
		RS=2,
		WS=1
	};

	tms5220_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);

	// configuration helpers
	template <class Object> devcb_base &set_irq_handler(Object &&cb) { return m_irq_handler.set_callback(std::forward<Object>(cb)); }
	template <class Object> devcb_base &set_readyq_handler(Object &&cb) { return m_readyq_handler.set_callback(std::forward<Object>(cb)); }
	// old VSM support, remove me!
	void set_speechrom_tag(const char *_tag) { m_speechrom_tag = _tag; }
	// new VSM support
	template <class Object> devcb_base &set_m0_callback(Object &&cb) { return m_m0_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> devcb_base &set_m1_callback(Object &&cb) { return m_m1_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> devcb_base &set_addr_callback(Object &&cb) { return m_addr_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> devcb_base &set_data_callback(Object &&cb) { return m_data_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> devcb_base &set_romclk_callback(Object &&cb) { return m_romclk_cb.set_callback(std::forward<Object>(cb)); }

	// Control lines - once written to will switch interface into * "true" timing behaviour.

	// all lines with suffix q are active low!

	WRITE_LINE_MEMBER( rsq_w );
	WRITE_LINE_MEMBER( wsq_w );

	DECLARE_WRITE8_MEMBER( combined_rsq_wsq_w );
	/* this combined_rsq_wsq_w hack is necessary for specific systems such as
	the TI 99/8 since the 5220c and cd2501ecd do specific things if both lines
	go active or inactive at slightly different times by separate write_line
	writes, which causes the chip to incorrectly reset itself on the 99/8,
	where the writes are supposed to happen simultaneously;
	/RS is bit 1, /WS is bit 0
	Note this is a hack and probably can be removed later, once the 'real'
	line handlers above defer by at least 4 clock cycles before taking effect */
	DECLARE_WRITE8_MEMBER( data_w ) { write_data(data); }
	DECLARE_READ8_MEMBER( status_r ) { return read_status(); }

	void write_data(uint8_t data);
	uint8_t read_status();

	READ_LINE_MEMBER( readyq_r );
	READ_LINE_MEMBER( intq_r );

protected:
	tms5220_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, int variant);

	// device-level overrides
	virtual void device_start() override;
	virtual void device_reset() override;
	virtual void device_clock_changed() override;

	virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;

	// sound stream update overrides
	virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;

private:
	static constexpr unsigned FIFO_SIZE = 16;

	// 51xx and VSM related
	void new_int_write(uint8_t rc, uint8_t m0, uint8_t m1, uint8_t addr);
	void new_int_write_addr(uint8_t addr);
	uint8_t new_int_read();
	void perform_dummy_read();
	// 52xx or common
	void register_for_save_states();
	void data_write(int data);
	void update_fifo_status_and_ints();
	int extract_bits(int count);
	uint8_t status_read(bool clear_int);
	bool ready_read();
	bool int_read();
	void process(int16_t *buffer, unsigned int size);
	int16_t clip_analog(int16_t cliptemp) const;
	int32_t matrix_multiply(int32_t a, int32_t b) const;
	int32_t lattice_filter();
	void process_command(unsigned char cmd);
	void parse_frame();
	void set_interrupt_state(int state);
	void update_ready_state();

	bool talk_status() const { return m_SPEN || m_TALKD; }
	bool &old_frame_silence_flag() { return m_OLDE; } // 1 if E=0, 0 otherwise.
	bool &old_frame_unvoiced_flag() { return m_OLDP; } // 1 if P=0 (unvoiced), 0 if voiced
	bool new_frame_stop_flag() const { return m_new_frame_energy_idx == 0x0F; } // 1 if this is a stop (Energy = 0xF) frame
	bool new_frame_silence_flag() const { return m_new_frame_energy_idx == 0; } // ditto as above
	bool new_frame_unvoiced_flag() const { return m_new_frame_pitch_idx == 0; } // ditto as above

	// debugging helper
	void printbits(long data, int num);

	// internal state

	/* coefficient tables */
	const int m_variant;                /* Variant of the 5xxx - see tms5110r.h */

	/* coefficient tables */
	const struct tms5100_coeffs *m_coeff;

	/* these contain global status bits for the 5100 */
	uint8_t m_PDC;
	uint8_t m_CTL_pins;
	uint8_t m_state;

	/* New VSM interface */
	uint32_t m_address;
	bool  m_next_is_address;
	bool  m_schedule_dummy_read;
	uint8_t  m_addr_bit;
	/* read byte */
	uint8_t  m_CTL_buffer;

	/* Old VSM interface; R Nabet : These have been added to emulate speech Roms */
	//bool m_schedule_dummy_read;          /* set after each load address, so that next read operation is preceded by a dummy read */
	uint8_t m_read_byte_register;       /* a serial->parallel shifter, used by "read byte" command to store 8 bits from the VSM */
	bool m_RDB_flag;                   /* whether we should read data register or status register */

	/* these contain data that describes the 128-bit data FIFO */
	uint8_t m_fifo[FIFO_SIZE];
	uint8_t m_fifo_head;
	uint8_t m_fifo_tail;
	uint8_t m_fifo_count;
	uint8_t m_fifo_bits_taken;


	/* these contain global status bits (booleans) */
	bool m_previous_talk_status;/* this is the OLD value of talk_status (i.e. previous value of m_SPEN|m_TALKD), needed for generating interrupts on a falling talk_status edge */
	bool m_SPEN;                /* set on speak(or speak external and BL falling edge) command, cleared on stop command, reset command, or buffer out */
	bool m_DDIS;                /* If 1, DDIS is 1, i.e. Speak External command in progress, writes go to FIFO. */
	bool m_TALK;                /* set on SPEN & RESETL4(pc12->pc0 transition), cleared on stop command or reset command */
	bool m_TALKD;               /* TALK(TCON) value, latched every RESETL4 */
	bool m_buffer_low;          /* If 1, FIFO has less than 8 bytes in it */
	bool m_buffer_empty;        /* If 1, FIFO is empty */
	bool m_irq_pin;             /* state of the IRQ pin (output) */
	bool m_ready_pin;           /* state of the READY pin (output) */

	/* these contain data describing the current and previous voice frames */
	bool m_OLDE;
	bool m_OLDP;

	uint8_t m_new_frame_energy_idx;
	uint8_t m_new_frame_pitch_idx;
	uint8_t m_new_frame_k_idx[10];


	/* these are all used to contain the current state of the sound generation */
#ifndef TMS5220_PERFECT_INTERPOLATION_HACK
	int16_t m_current_energy;
	int16_t m_current_pitch;
	int16_t m_current_k[10];
#else
	uint8_t m_old_frame_energy_idx;
	uint8_t m_old_frame_pitch_idx;
	uint8_t m_old_frame_k_idx[10];
	bool m_old_zpar;
	bool m_old_uv_zpar;

	int32_t m_current_energy;
	int32_t m_current_pitch;
	int32_t m_current_k[10];
#endif

	uint16_t m_previous_energy; /* needed for lattice filter to match patent */

	uint8_t m_subcycle;         /* contains the current subcycle for a given PC: 0 is A' (only used on SPKSLOW mode on 51xx), 1 is A, 2 is B */
	uint8_t m_subc_reload;      /* contains 1 for normal speech, 0 when SPKSLOW is active */
	uint8_t m_PC;               /* current parameter counter (what param is being interpolated), ranges from 0 to 12 */
	/* NOTE: the interpolation period counts 1,2,3,4,5,6,7,0 for divide by 8,8,8,4,4,2,2,1 */
	uint8_t m_IP;               /* the current interpolation period */
	bool m_inhibit;             /* If 1, interpolation is inhibited until the DIV1 period */
	bool m_uv_zpar;             /* If 1, zero k5 thru k10 coefficients */
	bool m_zpar;                /* If 1, zero ALL parameters. */
	bool m_pitch_zero;          /* circuit 412; pitch is forced to zero under certain circumstances */
	uint8_t m_c_variant_rate;   /* only relevant for tms5220C's multi frame rate feature; is the actual 4 bit value written on a 0x2* or 0x0* command */
	uint16_t m_pitch_count;     /* pitch counter; provides chirp rom address */

	int32_t m_u[11];
	int32_t m_x[10];

	uint16_t m_RNG;             /* the random noise generator configuration is: 1 + x + x^3 + x^4 + x^13 TODO: no it isn't */
	int16_t m_excitation_data;

	/* The TMS52xx has two different ways of providing output data: the
	   analog speaker pin (which was usually used) and the Digital I/O pin.
	   The internal DAC used to feed the analog pin is only 8 bits, and has the
	   funny clipping/clamping logic, while the digital pin gives full 10 bit
	   resolution of the output data.
	   TODO: add an MCFG macro to set this other than the FORCE_DIGITAL define
	 */
	bool m_digital_select;

	/* io_ready: page 3 of the datasheet specifies that READY will be asserted until
	 * data is available or processed by the system.
	 */
	bool m_io_ready;

	/* flag for "true" timing involving rs/ws */
	bool m_true_timing;

	/* rsws - state, rs bit 1, ws bit 0 */
	uint8_t m_rs_ws;
	uint8_t m_read_latch;
	uint8_t m_write_latch;

	sound_stream *m_stream;
	emu_timer *m_timer_io_ready;

	/* callbacks */
	devcb_write_line m_irq_handler;
	devcb_write_line m_readyq_handler;
	// next 2 lines are old speechrom handler, remove me!
	const char *m_speechrom_tag;
	speechrom_device *m_speechrom;
	// next lines are new speechrom handler
	devcb_write_line   m_m0_cb;      // the M0 line
	devcb_write_line   m_m1_cb;      // the M1 line
	devcb_write8       m_addr_cb;    // Write to ADD1,2,4,8 - 4 address bits
	devcb_read_line    m_data_cb;    // Read one bit from ADD8/Data - voice data
	// On a real chip rom_clk is running all the time
	// Here, we only use it to properly emulate the protocol.
	// Do not rely on it to be a timed signal.
	devcb_write_line   m_romclk_cb;  // rom clock - Only used to drive the data lines
};


class tms5220c_device : public tms5220_device
{
public:
	tms5220c_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};


class cd2501e_device : public tms5220_device
{
public:
	cd2501e_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};


class tms5200_device : public tms5220_device
{
public:
	tms5200_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};


class cd2501ecd_device : public tms5220_device
{
public:
	cd2501ecd_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};


DECLARE_DEVICE_TYPE(TMS5220,   tms5220_device)
DECLARE_DEVICE_TYPE(TMS5220C,  tms5220c_device)
DECLARE_DEVICE_TYPE(CD2501E,   cd2501e_device)
DECLARE_DEVICE_TYPE(TMS5200,   tms5200_device)
DECLARE_DEVICE_TYPE(CD2501ECD, cd2501ecd_device)

#endif // MAME_SOUND_TMS5220_H