trunk/src/mess/drivers/esq1.c
| r22651 | r22652 | |
| 3 | 3 | drivers/esq1.c |
| 4 | 4 | |
| 5 | 5 | Ensoniq ESQ-1 Digital Wave Synthesizer |
| 6 | Ensoniq ESQ-M (rack-mount ESQ-1) |
| 6 | 7 | Ensoniq SQ-80 Cross Wave Synthesizer |
| 7 | | Driver by R. Belmont |
| 8 | Driver by R. Belmont and O. Galibert |
| 8 | 9 | |
| 9 | 10 | Map for ESQ-1 and ESQ-m: |
| 10 | 11 | 0000-1fff: OS RAM |
| r22651 | r22652 | |
| 109 | 110 | 35 = MODES |
| 110 | 111 | 36 = SPLIT / LAYER |
| 111 | 112 | |
| 113 | |
| 114 | Analog filters (CEM3379): |
| 115 | |
| 116 | The analog part is relatively simple. The digital part outputs 8 |
| 117 | voices, which are filtered, amplified, panned then summed |
| 118 | together. |
| 119 | |
| 120 | The filtering stage is a 4-level lowpass filter with a loopback: |
| 121 | |
| 122 | |
| 123 | +-[+]-<-[*-1]--------------------------+ |
| 124 | | | | |
| 125 | ^ [*r] | |
| 126 | | | | |
| 127 | | v ^ |
| 128 | input ---+-[+]--[LPF]---[LPF]---[LPF]---[LPF]---+--- output |
| 129 | |
| 130 | All 4 LPFs are identical, with a transconductance G: |
| 131 | |
| 132 | output = 1/(1+s/G)^4 * ( (1+r)*input - r*output) |
| 133 | |
| 134 | or |
| 135 | |
| 136 | output = input * (1+r)/((1+s/G)^4+r) |
| 137 | |
| 138 | to which the usual z-transform can be applied (see votrax.c) |
| 139 | |
| 140 | G is voltage controlled through the Vfreq input, with the formula (Vfreq in mV): |
| 141 | |
| 142 | G = 6060*exp(Vfreq/28.5) |
| 143 | |
| 144 | That gives a cutoff frequency (f=G/(2pi)) of 5Hz at 5mV, 964Hz at |
| 145 | 28.5mV and 22686Hz at 90mV. The resistor ladder between the DAC |
| 146 | and the input seem to map 0..255 into a range of -150.4mV to |
| 147 | +83.6mV. |
| 148 | |
| 149 | The resonance is controlled through the Vq input pin, and is not |
| 150 | well defined. Reading between the lines the control seems linear |
| 151 | and tops when then circuit is self-oscillation, at r=4. |
| 152 | |
| 153 | The amplification is exponential for a control voltage between 0 |
| 154 | to 0.2V from -100dB to -20dB, and then linear up to 5V at 0dB. Or |
| 155 | in other words: |
| 156 | amp(Vca) = Vca < 0.2 ? 10**(-5+20*Vca) : Vca*0.1875 + 0.0625 |
| 157 | |
| 158 | |
| 159 | Finally the panning is not very described. What is clear is that |
| 160 | the control voltage at 2.5V gives a gain of -6dB, the max |
| 161 | attenuation at 0/5V is -100dB. The doc also says the gain is |
| 162 | linear between 1V and 3.5V, which makes no sense since it's not |
| 163 | symmetrical, and logarithmic afterwards, probably meaning |
| 164 | exponential, otherwise the change between 0 and 1V would be |
| 165 | minimal. So we're going to do some assumptions: |
| 166 | - 0-1V exponential from -100Db to -30dB |
| 167 | - 1V-2.5V linear from -30dB to -6dB |
| 168 | - 2.5V-5V is 1-amp at 2.5V-v |
| 169 | |
| 170 | Note that this may be incorrect, maybe to sum of squares should be |
| 171 | constant, the half-point should be at -3dB and the linearity in dB |
| 172 | space. |
| 173 | |
| 174 | |
| 112 | 175 | ***************************************************************************/ |
| 113 | 176 | |
| 114 | 177 | #include "emu.h" |
| r22651 | r22652 | |
| 124 | 187 | |
| 125 | 188 | #define WD1772_TAG "wd1772" |
| 126 | 189 | |
| 127 | | // QWERTYU = a few keys |
| 128 | | // top row 1-0 = the soft keys above and below the display (patch select) |
| 190 | class esq1_filters : public device_t, |
| 191 | public device_sound_interface |
| 192 | { |
| 193 | public: |
| 194 | // construction/destruction |
| 195 | esq1_filters(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); |
| 129 | 196 | |
| 197 | void set_vca(int channel, UINT8 value); |
| 198 | void set_vpan(int channel, UINT8 value); |
| 199 | void set_vq(int channel, UINT8 value); |
| 200 | void set_vfc(int channel, UINT8 value); |
| 201 | |
| 202 | protected: |
| 203 | // device-level overrides |
| 204 | virtual void device_start(); |
| 205 | |
| 206 | // device_sound_interface overrides |
| 207 | virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples); |
| 208 | |
| 209 | private: |
| 210 | struct filter { |
| 211 | UINT8 vca, vpan, vq, vfc; |
| 212 | double amp, lamp, ramp; |
| 213 | double a[5], b[5]; |
| 214 | double x[4], y[4]; |
| 215 | }; |
| 216 | |
| 217 | filter filters[8]; |
| 218 | |
| 219 | sound_stream *stream; |
| 220 | |
| 221 | void recalc_filter(filter &f); |
| 222 | }; |
| 223 | |
| 224 | static const device_type ESQ1_FILTERS = &device_creator<esq1_filters>; |
| 225 | |
| 226 | esq1_filters::esq1_filters(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 227 | : device_t(mconfig, ESQ1_FILTERS, "ESQ1 Filters stage", tag, owner, clock, "esq1-filters", __FILE__), |
| 228 | device_sound_interface(mconfig, *this) |
| 229 | { |
| 230 | } |
| 231 | |
| 232 | void esq1_filters::set_vca(int channel, UINT8 value) |
| 233 | { |
| 234 | if(filters[channel].vca != value) { |
| 235 | stream->update(); |
| 236 | filters[channel].vca = value; |
| 237 | recalc_filter(filters[channel]); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | void esq1_filters::set_vpan(int channel, UINT8 value) |
| 242 | { |
| 243 | if(filters[channel].vpan != value) { |
| 244 | stream->update(); |
| 245 | filters[channel].vpan = value; |
| 246 | recalc_filter(filters[channel]); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | void esq1_filters::set_vq(int channel, UINT8 value) |
| 251 | { |
| 252 | if(filters[channel].vq != value) { |
| 253 | stream->update(); |
| 254 | filters[channel].vq = value; |
| 255 | recalc_filter(filters[channel]); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | void esq1_filters::set_vfc(int channel, UINT8 value) |
| 260 | { |
| 261 | if(filters[channel].vfc != value) { |
| 262 | stream->update(); |
| 263 | filters[channel].vfc = value; |
| 264 | recalc_filter(filters[channel]); |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | void esq1_filters::recalc_filter(filter &f) |
| 269 | { |
| 270 | // Filtering stage |
| 271 | // First let's establish the control values |
| 272 | // Some tuning may be required |
| 273 | |
| 274 | double vfc = -150.4 + (83.6+150.4)*f.vfc/255; |
| 275 | double r = 4.0*f.vq/255; |
| 276 | |
| 277 | |
| 278 | double g = 6060*exp(vfc/28.5); |
| 279 | double zc = g/tan(g/2/44100); |
| 280 | |
| 281 | /* if(f.vfc) { |
| 282 | double ff = g/(2*M_PI); |
| 283 | double fzc = 2*M_PI*ff/tan(M_PI*ff/44100); |
| 284 | fprintf(stderr, "%02x f=%f zc=%f zc1=%f\n", f.vfc, g/(2*M_PI), zc, fzc); |
| 285 | }*/ |
| 286 | |
| 287 | double gzc = zc/g; |
| 288 | double gzc2 = gzc*gzc; |
| 289 | double gzc3 = gzc2*gzc; |
| 290 | double gzc4 = gzc3*gzc; |
| 291 | double r1 = 1+r; |
| 292 | |
| 293 | f.a[0] = r1; |
| 294 | f.a[1] = 4*r1; |
| 295 | f.a[2] = 6*r1; |
| 296 | f.a[3] = 4*r1; |
| 297 | f.a[4] = r1; |
| 298 | |
| 299 | f.b[0] = r1 + 4*gzc + 6*gzc2 + 4*gzc3 + gzc4; |
| 300 | f.b[1] = 4*(r1 + 2*gzc - 2*gzc3 - gzc4); |
| 301 | f.b[2] = 6*(r1 - 2*gzc2 + gzc4); |
| 302 | f.b[3] = 4*(r1 - 2*gzc + 2*gzc3 - gzc4); |
| 303 | f.b[4] = r1 - 4*gzc + 6*gzc2 - 4*gzc3 + gzc4; |
| 304 | |
| 305 | /* if(f.vfc != 0) |
| 306 | for(int i=0; i<5; i++) |
| 307 | printf("a%d=%f\nb%d=%f\n", |
| 308 | i, f.a[i], i, f.b[i]);*/ |
| 309 | |
| 310 | // Amplification stage |
| 311 | double vca = f.vca*(5.0/255.0); |
| 312 | f.amp = vca < 0.2 ? pow(10, -5+20*vca) : vca*0.1875 + 0.0625; |
| 313 | |
| 314 | // Panning stage |
| 315 | // Very approximative at best |
| 316 | // Left/right unverified |
| 317 | double vpan = f.vpan*(5.0/255.0); |
| 318 | double vref = vpan > 2.5 ? 2.5 - vpan : vpan; |
| 319 | double pan_amp = vref < 1 ? pow(10, -5+3.5*vref) : vref*0.312 - 0.280; |
| 320 | if(vref < 2.5) { |
| 321 | f.lamp = pan_amp; |
| 322 | f.ramp = 1-pan_amp; |
| 323 | } else { |
| 324 | f.lamp = 1-pan_amp; |
| 325 | f.ramp = pan_amp; |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | void esq1_filters::device_start() |
| 330 | { |
| 331 | stream = stream_alloc(8, 2, 44100); |
| 332 | memset(filters, 0, sizeof(filters)); |
| 333 | for(int i=0; i<8; i++) |
| 334 | recalc_filter(filters[i]); |
| 335 | } |
| 336 | |
| 337 | void esq1_filters::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) |
| 338 | { |
| 339 | /* if(0) { |
| 340 | for(int i=0; i<8; i++) |
| 341 | fprintf(stderr, " [%02x %02x %02x %02x]", |
| 342 | filters[i].vca, |
| 343 | filters[i].vpan, |
| 344 | filters[i].vq, |
| 345 | filters[i].vfc); |
| 346 | fprintf(stderr, "\n"); |
| 347 | }*/ |
| 348 | |
| 349 | for(int i=0; i<samples; i++) { |
| 350 | double l=0, r=0; |
| 351 | for(int j=0; j<8; j++) { |
| 352 | filter &f = filters[j]; |
| 353 | double x = inputs[j][i]; |
| 354 | double y = (x*f.a[0] |
| 355 | + f.x[0]*f.a[1] + f.x[1]*f.a[2] + f.x[2]*f.a[3] + f.x[3]*f.a[4] |
| 356 | - f.y[0]*f.b[1] - f.y[1]*f.b[2] - f.y[2]*f.b[3] - f.y[3]*f.b[4]) / f.b[0]; |
| 357 | memmove(f.x+1, f.x, 3*sizeof(double)); |
| 358 | memmove(f.y+1, f.y, 3*sizeof(double)); |
| 359 | f.x[0] = x; |
| 360 | f.y[0] = y; |
| 361 | y = y * f.amp; |
| 362 | l += y * f.lamp; |
| 363 | r += y * f.ramp; |
| 364 | } |
| 365 | static double maxl = 0; |
| 366 | if(l > maxl) { |
| 367 | maxl = l; |
| 368 | // fprintf(stderr, "%f\n", maxl); |
| 369 | } |
| 370 | |
| 371 | // l *= 6553; |
| 372 | // r *= 6553; |
| 373 | l *= 2; |
| 374 | r *= 2; |
| 375 | outputs[0][i] = l < -32768 ? -32768 : l > 32767 ? 32767 : int(l); |
| 376 | outputs[1][i] = r < -32768 ? -32768 : r > 32767 ? 32767 : int(r); |
| 377 | } |
| 378 | } |
| 379 | |
| 130 | 380 | class esq1_state : public driver_device |
| 131 | 381 | { |
| 132 | 382 | public: |
| r22651 | r22652 | |
| 134 | 384 | : driver_device(mconfig, type, tag), |
| 135 | 385 | m_maincpu(*this, "maincpu"), |
| 136 | 386 | m_duart(*this, "duart"), |
| 387 | m_filters(*this, "filters"), |
| 137 | 388 | m_fdc(*this, WD1772_TAG), |
| 138 | 389 | m_panel(*this, "panel"), |
| 139 | 390 | m_mdout(*this, "mdout") |
| r22651 | r22652 | |
| 141 | 392 | |
| 142 | 393 | required_device<cpu_device> m_maincpu; |
| 143 | 394 | required_device<duartn68681_device> m_duart; |
| 395 | required_device<esq1_filters> m_filters; |
| 144 | 396 | optional_device<wd1772_t> m_fdc; |
| 145 | 397 | optional_device<esqpanel2x40_device> m_panel; |
| 146 | 398 | optional_device<serial_port_device> m_mdout; |
| r22651 | r22652 | |
| 150 | 402 | DECLARE_READ8_MEMBER(seqdosram_r); |
| 151 | 403 | DECLARE_WRITE8_MEMBER(seqdosram_w); |
| 152 | 404 | DECLARE_WRITE8_MEMBER(mapper_w); |
| 405 | DECLARE_WRITE8_MEMBER(analog_w); |
| 153 | 406 | |
| 154 | 407 | DECLARE_WRITE_LINE_MEMBER(duart_irq_handler); |
| 155 | 408 | DECLARE_WRITE_LINE_MEMBER(duart_tx_a); |
| r22651 | r22652 | |
| 203 | 456 | // printf("mapper_state = %d\n", data ^ 1); |
| 204 | 457 | } |
| 205 | 458 | |
| 459 | WRITE8_MEMBER(esq1_state::analog_w) |
| 460 | { |
| 461 | if(!(offset & 8)) |
| 462 | m_filters->set_vfc(offset & 7, data); |
| 463 | if(!(offset & 16)) |
| 464 | m_filters->set_vq(offset & 7, data); |
| 465 | if(!(offset & 32)) |
| 466 | m_filters->set_vpan(offset & 7, data); |
| 467 | if(!(offset & 64)) |
| 468 | m_filters->set_vca(offset & 7, data); |
| 469 | } |
| 470 | |
| 206 | 471 | READ8_MEMBER(esq1_state::seqdosram_r) |
| 207 | 472 | { |
| 208 | 473 | if (m_mapper_state) |
| r22651 | r22652 | |
| 232 | 497 | AM_RANGE(0x4000, 0x5fff) AM_RAM // SEQRAM |
| 233 | 498 | AM_RANGE(0x6000, 0x63ff) AM_DEVREADWRITE("es5503", es5503_device, read, write) |
| 234 | 499 | AM_RANGE(0x6400, 0x640f) AM_DEVREADWRITE("duart", duartn68681_device, read, write) |
| 235 | | AM_RANGE(0x6800, 0x68ff) AM_NOP |
| 236 | | |
| 500 | AM_RANGE(0x6800, 0x68ff) AM_WRITE(analog_w) |
| 237 | 501 | AM_RANGE(0x7000, 0x7fff) AM_ROMBANK("osbank") |
| 238 | 502 | AM_RANGE(0x8000, 0xffff) AM_ROM AM_REGION("osrom", 0x8000) // OS "high" ROM is always mapped here |
| 239 | 503 | ADDRESS_MAP_END |
| r22651 | r22652 | |
| 244 | 508 | // AM_RANGE(0x4000, 0x5fff) AM_READWRITE(seqdosram_r, seqdosram_w) |
| 245 | 509 | AM_RANGE(0x6000, 0x63ff) AM_DEVREADWRITE("es5503", es5503_device, read, write) |
| 246 | 510 | AM_RANGE(0x6400, 0x640f) AM_DEVREADWRITE("duart", duartn68681_device, read, write) |
| 511 | AM_RANGE(0x6800, 0x68ff) AM_WRITE(analog_w) |
| 247 | 512 | AM_RANGE(0x6c00, 0x6dff) AM_WRITE(mapper_w) |
| 248 | 513 | AM_RANGE(0x6e00, 0x6fff) AM_READWRITE(wd1772_r, wd1772_w) |
| 249 | 514 | AM_RANGE(0x7000, 0x7fff) AM_ROMBANK("osbank") |
| r22651 | r22652 | |
| 267 | 532 | |
| 268 | 533 | WRITE_LINE_MEMBER(esq1_state::duart_irq_handler) |
| 269 | 534 | { |
| 270 | | m_maincpu->set_input_line(0, state); |
| 535 | m_maincpu->set_input_line(M6809_IRQ_LINE, state); |
| 271 | 536 | }; |
| 272 | 537 | |
| 273 | 538 | READ8_MEMBER(esq1_state::duart_input) |
| r22651 | r22652 | |
| 290 | 555 | // MIDI send |
| 291 | 556 | WRITE_LINE_MEMBER(esq1_state::duart_tx_a) |
| 292 | 557 | { |
| 293 | | m_mdout->tx(state); |
| 558 | m_mdout->tx(state); |
| 294 | 559 | } |
| 295 | 560 | |
| 296 | 561 | WRITE_LINE_MEMBER(esq1_state::duart_tx_b) |
| 297 | 562 | { |
| 298 | | m_panel->rx_w(state); |
| 563 | m_panel->rx_w(state); |
| 299 | 564 | } |
| 300 | 565 | |
| 301 | 566 | void esq1_state::send_through_panel(UINT8 data) |
| 302 | 567 | { |
| 303 | | m_panel->xmit_char(data); |
| 568 | m_panel->xmit_char(data); |
| 304 | 569 | } |
| 305 | 570 | |
| 306 | 571 | INPUT_CHANGED_MEMBER(esq1_state::key_stroke) |
| 307 | 572 | { |
| 308 | | if (oldval == 0 && newval == 1) |
| 309 | | { |
| 310 | | send_through_panel((UINT8)(FPTR)param); |
| 311 | | send_through_panel((UINT8)(FPTR)0x00); |
| 312 | | } |
| 313 | | else if (oldval == 1 && newval == 0) |
| 314 | | { |
| 315 | | send_through_panel((UINT8)(FPTR)param&0x7f); |
| 316 | | send_through_panel((UINT8)(FPTR)0x00); |
| 317 | | } |
| 573 | if (oldval == 0 && newval == 1) |
| 574 | { |
| 575 | send_through_panel((UINT8)(FPTR)param); |
| 576 | send_through_panel((UINT8)(FPTR)0x00); |
| 577 | } |
| 578 | else if (oldval == 1 && newval == 0) |
| 579 | { |
| 580 | send_through_panel((UINT8)(FPTR)param&0x7f); |
| 581 | send_through_panel((UINT8)(FPTR)0x00); |
| 582 | } |
| 318 | 583 | } |
| 319 | 584 | |
| 320 | 585 | static SLOT_INTERFACE_START(midiin_slot) |
| r22651 | r22652 | |
| 362 | 627 | MCFG_SERIAL_PORT_ADD("mdout", midiout_intf, midiout_slot, "midiout", NULL) |
| 363 | 628 | |
| 364 | 629 | MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker") |
| 365 | | MCFG_ES5503_ADD("es5503", 7000000, 8, esq1_doc_irq, esq1_adc_read) |
| 630 | |
| 631 | MCFG_SOUND_ADD("filters", ESQ1_FILTERS, 0) |
| 366 | 632 | MCFG_SOUND_ROUTE(0, "lspeaker", 1.0) |
| 367 | 633 | MCFG_SOUND_ROUTE(1, "rspeaker", 1.0) |
| 368 | | MCFG_SOUND_ROUTE(2, "lspeaker", 1.0) |
| 369 | | MCFG_SOUND_ROUTE(3, "rspeaker", 1.0) |
| 370 | | MCFG_SOUND_ROUTE(4, "lspeaker", 1.0) |
| 371 | | MCFG_SOUND_ROUTE(5, "rspeaker", 1.0) |
| 372 | | MCFG_SOUND_ROUTE(6, "lspeaker", 1.0) |
| 373 | | MCFG_SOUND_ROUTE(7, "rspeaker", 1.0) |
| 634 | |
| 635 | MCFG_ES5503_ADD("es5503", 7000000, 8, esq1_doc_irq, esq1_adc_read) |
| 636 | MCFG_SOUND_ROUTE_EX(0, "filters", 1.0, 0) |
| 637 | MCFG_SOUND_ROUTE_EX(1, "filters", 1.0, 1) |
| 638 | MCFG_SOUND_ROUTE_EX(2, "filters", 1.0, 2) |
| 639 | MCFG_SOUND_ROUTE_EX(3, "filters", 1.0, 3) |
| 640 | MCFG_SOUND_ROUTE_EX(4, "filters", 1.0, 4) |
| 641 | MCFG_SOUND_ROUTE_EX(5, "filters", 1.0, 5) |
| 642 | MCFG_SOUND_ROUTE_EX(6, "filters", 1.0, 6) |
| 643 | MCFG_SOUND_ROUTE_EX(7, "filters", 1.0, 7) |
| 374 | 644 | MACHINE_CONFIG_END |
| 375 | 645 | |
| 376 | 646 | static MACHINE_CONFIG_DERIVED(sq80, esq1) |
| r22651 | r22652 | |
| 435 | 705 | ROM_LOAD( "sq80_kpc_150.bin", 0x000000, 0x008000, CRC(8170b728) SHA1(3ad68bb03948e51b20d2e54309baa5c02a468f7c) ) |
| 436 | 706 | ROM_END |
| 437 | 707 | |
| 708 | ROM_START( esqm ) |
| 709 | ROM_REGION(0x10000, "osrom", 0) |
| 710 | ROM_LOAD( "1355500157_d640_esq-m_oshi.u14", 0x8000, 0x008000, CRC(ea6a7bae) SHA1(2830f8c52dc443b4ca469dc190b33e2ff15b78e1) ) |
| 711 | |
| 712 | ROM_REGION(0x20000, "es5503", 0) |
| 713 | ROM_LOAD( "esq1wavlo.bin", 0x0000, 0x8000, CRC(4d04ac87) SHA1(867b51229b0a82c886bf3b216aa8893748236d8b) ) |
| 714 | ROM_LOAD( "esq1wavhi.bin", 0x8000, 0x8000, CRC(94c554a3) SHA1(ed0318e5253637585559e8cf24c06d6115bd18f6) ) |
| 715 | ROM_END |
| 716 | |
| 717 | |
| 438 | 718 | CONS( 1986, esq1, 0 , 0, esq1, esq1, driver_device, 0, "Ensoniq", "ESQ-1", GAME_NOT_WORKING ) |
| 719 | CONS( 1986, esqm, esq1, 0, esq1, esq1, driver_device, 0, "Ensoniq", "ESQ-M", GAME_NOT_WORKING ) |
| 439 | 720 | CONS( 1988, sq80, 0, 0, sq80, esq1, driver_device, 0, "Ensoniq", "SQ-80", GAME_NOT_WORKING ) |
| 721 | |