1 // license:BSD-3-Clause
2 // copyright-holders:Olivier Galibert
3 /*
4 GI SP0250 digital LPC sound synthesizer
5
6 By O. Galibert.
7
8 Unimplemented:
9 - Direct Data test mode (pin 7)
10 */
11
12 #include "emu.h"
13 #include "sp0250.h"
14
15 //
16 // Input clock is divided by 2 to make ROMCLOCK.
17 // Output is via pulse-width modulation (PWM) over the course of 39 ROMCLOCKs.
18 // 4 PWM periods per frame.
19 //
20 static constexpr int PWM_CLOCKS = 39;
21
22
23 DEFINE_DEVICE_TYPE(SP0250, sp0250_device, "sp0250", "GI SP0250 LPC")
24
sp0250_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)25 sp0250_device::sp0250_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
26 device_t(mconfig, SP0250, tag, owner, clock),
27 device_sound_interface(mconfig, *this),
28 m_pwm_mode(false),
29 m_pwm_index(PWM_CLOCKS),
30 m_pwm_count(0),
31 m_pwm_counts(0),
32 m_voiced(0),
33 m_amp(0),
34 m_lfsr(0x7fff),
35 m_pitch(0),
36 m_pcount(0),
37 m_repeat(0),
38 m_rcount(0),
39 m_fifo_pos(0),
40 m_stream(nullptr),
41 m_drq(*this)
42 {
43 for (auto & elem : m_fifo)
44 {
45 elem = 0;
46 }
47
48 for (auto & elem : m_filter)
49 {
50 elem.F = 0;
51 elem.B = 0;
52 elem.z1 = 0;
53 elem.z2 = 0;
54 }
55 }
56
57 //-------------------------------------------------
58 // device_start - device-specific startup
59 //-------------------------------------------------
60
device_start()61 void sp0250_device::device_start()
62 {
63 // output PWM data at the ROMCLOCK frequency
64 int sample_rate = clock() / 2;
65 int frame_rate = sample_rate / (4 * PWM_CLOCKS);
66 m_stream = stream_alloc(0, 1, m_pwm_mode ? sample_rate : frame_rate);
67
68 // if a DRQ callback is offered, run a timer at the frame rate
69 // to ensure the DRQ gets picked up in a timely manner
70 m_drq.resolve_safe();
71 if (!m_drq.isnull())
72 {
73 m_drq(ASSERT_LINE);
74 attotime period = attotime::from_hz(frame_rate);
75 timer_alloc()->adjust(period, 0, period);
76 }
77
78 // PWM state
79 save_item(NAME(m_pwm_index));
80 save_item(NAME(m_pwm_count));
81 save_item(NAME(m_pwm_counts));
82
83 // LPC state
84 save_item(NAME(m_voiced));
85 save_item(NAME(m_amp));
86 save_item(NAME(m_lfsr));
87 save_item(NAME(m_pitch));
88 save_item(NAME(m_pcount));
89 save_item(NAME(m_repeat));
90 save_item(NAME(m_rcount));
91
92 save_item(STRUCT_MEMBER(m_filter, F));
93 save_item(STRUCT_MEMBER(m_filter, B));
94 save_item(STRUCT_MEMBER(m_filter, z1));
95 save_item(STRUCT_MEMBER(m_filter, z2));
96
97 // FIFO state
98 save_item(NAME(m_fifo));
99 save_item(NAME(m_fifo_pos));
100 }
101
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)102 void sp0250_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
103 {
104 m_stream->update();
105 }
106
sp0250_ga(uint8_t v)107 static uint16_t sp0250_ga(uint8_t v)
108 {
109 return (v & 0x1f) << (v>>5);
110 }
111
sp0250_gc(uint8_t v)112 static int16_t sp0250_gc(uint8_t v)
113 {
114 // Internal ROM to the chip, cf. manual
115 static const uint16_t coefs[128] =
116 {
117 0, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 105, 113, 121,
118 129, 137, 145, 153, 161, 169, 177, 185, 193, 201, 203, 217, 225, 233, 241, 249,
119 257, 265, 273, 281, 289, 297, 301, 305, 309, 313, 317, 321, 325, 329, 333, 337,
120 341, 345, 349, 353, 357, 361, 365, 369, 373, 377, 381, 385, 389, 393, 397, 401,
121 405, 409, 413, 417, 421, 425, 427, 429, 431, 433, 435, 437, 439, 441, 443, 445,
122 447, 449, 451, 453, 455, 457, 459, 461, 463, 465, 467, 469, 471, 473, 475, 477,
123 479, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495,
124 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511
125 };
126 int16_t res = coefs[v & 0x7f];
127
128 if (!(v & 0x80))
129 res = -res;
130 return res;
131 }
132
load_values()133 void sp0250_device::load_values()
134 {
135 m_filter[0].B = sp0250_gc(m_fifo[ 0]);
136 m_filter[0].F = sp0250_gc(m_fifo[ 1]);
137 m_amp = sp0250_ga(m_fifo[ 2]);
138 m_filter[1].B = sp0250_gc(m_fifo[ 3]);
139 m_filter[1].F = sp0250_gc(m_fifo[ 4]);
140 m_pitch = m_fifo[ 5];
141 m_filter[2].B = sp0250_gc(m_fifo[ 6]);
142 m_filter[2].F = sp0250_gc(m_fifo[ 7]);
143 m_repeat = m_fifo[ 8] & 0x3f;
144 m_voiced = m_fifo[ 8] & 0x40;
145 m_filter[3].B = sp0250_gc(m_fifo[ 9]);
146 m_filter[3].F = sp0250_gc(m_fifo[10]);
147 m_filter[4].B = sp0250_gc(m_fifo[11]);
148 m_filter[4].F = sp0250_gc(m_fifo[12]);
149 m_filter[5].B = sp0250_gc(m_fifo[13]);
150 m_filter[5].F = sp0250_gc(m_fifo[14]);
151 m_fifo_pos = 0;
152 m_drq(ASSERT_LINE);
153 m_pcount = 0;
154 m_rcount = 0;
155
156 for (int f = 0; f < 6; f++)
157 m_filter[f].reset();
158 }
159
write(uint8_t data)160 void sp0250_device::write(uint8_t data)
161 {
162 m_stream->update();
163 if (m_fifo_pos != 15)
164 {
165 m_fifo[m_fifo_pos++] = data;
166 if (m_fifo_pos == 15)
167 m_drq(CLEAR_LINE);
168 }
169 else
170 logerror("%s: overflow SP0250 FIFO\n", machine().describe_context());
171 }
172
173
drq_r()174 uint8_t sp0250_device::drq_r()
175 {
176 m_stream->update();
177 return (m_fifo_pos == 15) ? CLEAR_LINE : ASSERT_LINE;
178 }
179
next()180 int8_t sp0250_device::next()
181 {
182 if (m_rcount >= m_repeat)
183 {
184 if (m_fifo_pos == 15)
185 load_values();
186 else
187 {
188 // According to http://www.cpcwiki.eu/index.php/SP0256_Measured_Timings
189 // the SP0250 executes "NOPs" with a repeat count of 1 and unchanged
190 // pitch while waiting for input
191 m_repeat = 1;
192 m_pcount = 0;
193 m_rcount = 0;
194 }
195 }
196
197 // 15-bit LFSR algorithm verified by dump from actual hardware
198 // clocks every cycle regardless of voiced/unvoiced setting
199 m_lfsr ^= (m_lfsr ^ (m_lfsr >> 1)) << 15;
200 m_lfsr >>= 1;
201
202 int16_t z0;
203 if (m_voiced)
204 z0 = (m_pcount == 0) ? m_amp : 0;
205 else
206 z0 = (m_lfsr & 1) ? m_amp : -m_amp;
207
208 for (int f = 0; f < 6; f++)
209 z0 = m_filter[f].apply(z0);
210
211 // maximum amp value is effectively 13 bits
212 // reduce to 7 bits; due to filter effects it
213 // may occasionally clip
214 int dac = z0 >> 6;
215 if (dac < -64)
216 dac = -64;
217 if (dac > 63)
218 dac = 63;
219
220 // PWM is divided into 4x 5-bit sections; the lower
221 // bits of the original 7-bit value are added to only
222 // some of the pulses in the following pattern:
223 //
224 // DAC -64 -> 1,1,1,1
225 // DAC -63 -> 2,1,1,1
226 // DAC -62 -> 2,1,2,1
227 // DAC -61 -> 2,2,2,1
228 // DAC -60 -> 2,2,2,2
229 // ...
230 // DAC -1 -> 17,17,17,16
231 // DAC 0 -> 17,17,17,17
232 // DAC 1 -> 18,17,17,17
233 // ...
234 // DAC 60 -> 32,32,32,32
235 // DAC 61 -> 33,32,32,32
236 // DAC 62 -> 33,32,33,32
237 // DAC 63 -> 33,33,33,32
238 m_pwm_counts = (((dac + 68 + 3) >> 2) << 0) +
239 (((dac + 68 + 1) >> 2) << 8) +
240 (((dac + 68 + 2) >> 2) << 16) +
241 (((dac + 68 + 0) >> 2) << 24);
242
243 if (m_pcount++ == m_pitch)
244 {
245 m_pcount = 0;
246 m_rcount++;
247 }
248 return dac;
249 }
250
251 //-------------------------------------------------
252 // sound_stream_update - handle a stream update
253 //-------------------------------------------------
254
sound_stream_update(sound_stream & stream,std::vector<read_stream_view> const & inputs,std::vector<write_stream_view> & outputs)255 void sp0250_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
256 {
257 auto &output = outputs[0];
258
259 if (!m_pwm_mode)
260 {
261 for (int sampindex = 0; sampindex < output.samples(); sampindex++)
262 output.put_int(sampindex, next(), 128);
263 }
264 else
265 {
266 for (int sampindex = 0; sampindex < output.samples(); )
267 {
268 // see where we're at in the current PWM cycle
269 if (m_pwm_index >= PWM_CLOCKS)
270 {
271 m_pwm_index = 0;
272 if (m_pwm_counts == 0)
273 next();
274 m_pwm_count = m_pwm_counts & 0xff;
275 m_pwm_counts >>= 8;
276 }
277
278 // determine the value to fill and the number of samples remaining
279 // until it changes
280 stream_buffer::sample_t value;
281 int remaining;
282 if (m_pwm_index < m_pwm_count)
283 {
284 value = 1.0;
285 remaining = m_pwm_count - m_pwm_index;
286 }
287 else
288 {
289 value = 0.0;
290 remaining = PWM_CLOCKS - m_pwm_index;
291 }
292
293 // clamp to the number of samples requested and advance the counters
294 if (remaining > output.samples() - sampindex)
295 remaining = output.samples() - sampindex;
296 m_pwm_index += remaining;
297
298 // fill the output
299 while (remaining-- != 0)
300 outputs[0].put(sampindex++, value);
301 }
302 }
303 }
304